Overview
ETH Balance
0 ETH
Eth Value
$0.00More Info
Private Name Tags
ContractCreator
Latest 1 from a total of 1 transactions
Transaction Hash |
Method
|
Block
|
From
|
To
|
|||||
---|---|---|---|---|---|---|---|---|---|
0x60a03461 | 18416396 | 330 days ago | IN | 0 ETH | 0.04600083 |
View more zero value Internal Transactions in Advanced View mode
Advanced mode:
Loading...
Loading
This contract may be a proxy contract. Click on More Options and select Is this a proxy? to confirm and enable the "Read as Proxy" & "Write as Proxy" tabs.
Contract Name:
BasicMetadataProvider
Compiler Version
v0.8.20+commit.a1b79de6
Optimization Enabled:
Yes with 100 runs
Other Settings:
shanghai EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.20; import { Party } from "../party/Party.sol"; import { MetadataRegistry } from "./MetadataRegistry.sol"; import { MetadataProvider } from "./MetadataProvider.sol"; import { IGlobals } from "../globals/IGlobals.sol"; import { LibGlobals } from "../globals/LibGlobals.sol"; import { PartyNFTRenderer } from "./PartyNFTRenderer.sol"; /// @notice A contract that provides custom metadata for Party Cards. contract BasicMetadataProvider is MetadataProvider { error MetadataTooLarge(); constructor(IGlobals globals) MetadataProvider(globals) {} /// @inheritdoc MetadataProvider function getMetadata(address instance, uint256) external view override returns (bytes memory) { Metadata memory metadata; metadata.name = metadata.collectionName = Party(payable(instance)).name(); metadata.description = metadata.collectionDescription = retrieveDynamicMetadataInfo( instance, MetadataFields.DESCRIPTION ); metadata.externalURL = retrieveDynamicMetadataInfo(instance, MetadataFields.EXTERNAL_URL); metadata.image = retrieveDynamicMetadataInfo(instance, MetadataFields.IMAGE); metadata.banner = retrieveDynamicMetadataInfo(instance, MetadataFields.BANNER); metadata.animationURL = retrieveDynamicMetadataInfo(instance, MetadataFields.ANIMATION_URL); metadata.collectionExternalURL = retrieveDynamicMetadataInfo( instance, MetadataFields.COLLECTION_EXTERNAL_URL ); metadata.royaltyReceiver = address( uint160(uint256(retrieveValueMetadataInfo(instance, MetadataFields.ROYALTY_RECEIVER))) ); metadata.royaltyAmount = uint256( retrieveValueMetadataInfo(instance, MetadataFields.ROYALTY_AMOUNT) ); metadata.renderingMethod = PartyNFTRenderer.RenderingMethod( uint256(retrieveValueMetadataInfo(instance, MetadataFields.RENDERING_METHOD)) ); return abi.encode(metadata); } struct Metadata { string name; bytes description; bytes externalURL; bytes image; bytes banner; bytes animationURL; string collectionName; bytes collectionDescription; bytes collectionExternalURL; address royaltyReceiver; uint256 royaltyAmount; PartyNFTRenderer.RenderingMethod renderingMethod; } /// @notice Set the metadata for a Party instance. /// @param instance The address of the instance. /// @param metadata The encoded metadata. function setMetadata(address instance, bytes calldata metadata) external override { if (instance != msg.sender) { MetadataRegistry registry = MetadataRegistry( _GLOBALS.getAddress(LibGlobals.GLOBAL_METADATA_REGISTRY) ); // Check if the caller is authorized to set metadata for the instance. if (!registry.isRegistrar(msg.sender, instance)) { revert NotAuthorized(msg.sender, instance); } } Metadata memory decodedMetadata = abi.decode(metadata, (Metadata)); if (decodedMetadata.description.length != 0) { storeMetadataInfo( instance, MetadataFields.DESCRIPTION, decodedMetadata.description, false ); } if (decodedMetadata.externalURL.length != 0) { storeMetadataInfo( instance, MetadataFields.EXTERNAL_URL, decodedMetadata.externalURL, false ); } if (decodedMetadata.image.length != 0) { storeMetadataInfo(instance, MetadataFields.IMAGE, decodedMetadata.image, false); } if (decodedMetadata.banner.length != 0) { storeMetadataInfo(instance, MetadataFields.BANNER, decodedMetadata.banner, false); } if (decodedMetadata.animationURL.length != 0) { storeMetadataInfo( instance, MetadataFields.ANIMATION_URL, decodedMetadata.animationURL, false ); } if (decodedMetadata.collectionExternalURL.length != 0) { storeMetadataInfo( instance, MetadataFields.COLLECTION_EXTERNAL_URL, decodedMetadata.collectionExternalURL, false ); } if (decodedMetadata.royaltyReceiver != address(0)) { storeMetadataInfo( instance, MetadataFields.ROYALTY_RECEIVER, abi.encode(decodedMetadata.royaltyReceiver), true ); } if (decodedMetadata.royaltyAmount != 0) { storeMetadataInfo( instance, MetadataFields.ROYALTY_AMOUNT, abi.encode(decodedMetadata.royaltyAmount), true ); } if (decodedMetadata.renderingMethod != PartyNFTRenderer.RenderingMethod.ENUM_OFFSET) { storeMetadataInfo( instance, MetadataFields.RENDERING_METHOD, abi.encode(decodedMetadata.renderingMethod), true ); } emit MetadataSet(instance, metadata); } /// @notice The indexes of the metadata fields in storage (if dynamic contains a start slot) enum MetadataFields { DESCRIPTION, EXTERNAL_URL, IMAGE, BANNER, ANIMATION_URL, COLLECTION_EXTERNAL_URL, ROYALTY_RECEIVER, ROYALTY_AMOUNT, RENDERING_METHOD } /// @notice Stores a metadata field to storage /// @param instance The instance to store the metadata for /// @param field The field to store /// @param data The data to store /// @param isValue Whether the data is a value type or a dynamic type function storeMetadataInfo( address instance, MetadataFields field, bytes memory data, bool isValue ) private { uint256 metadataSlot; assembly { metadataSlot := _metadata.slot } uint256 slot = uint256(keccak256(abi.encode(instance, metadataSlot))) + uint8(field); uint256 value; assembly { value := mload(add(data, 0x20)) } if (!isValue) { // Check if we can force the data into a single slot uint256 dataLength = data.length; // We use the first bit as a signal that the data is an slot number if (dataLength > 32 || (dataLength == 32 && value >> 255 == 1)) { if (dataLength > type(uint16).max) { revert MetadataTooLarge(); } // Store the slot to the start of this data (first bit 1 indicates its a slot) uint256 dynamicSlot = (uint256(1) << 255) | uint256(keccak256(abi.encode(instance, metadataSlot, field))); assembly { // Store the dynamic data start slot in the value slot sstore(slot, dynamicSlot) } // Store first slot with first 16 bits as size uint16 length = uint16(data.length); assembly { sstore(dynamicSlot, or(shl(240, length), shr(16, mload(add(data, 0x20))))) } for (uint256 i = 30; i < data.length; i += 32) { uint256 mask = type(uint256).max; if (data.length < i + 32) { // Don't store the whole slot bc less than a slot worth of data mask = mask << (256 - (data.length - i) * 8); } uint256 slotIncrement = i + 2; bytes32 toStore; assembly { toStore := mload(add(i, add(0x20, data))) toStore := and(toStore, mask) sstore(add(dynamicSlot, slotIncrement), toStore) } } return; } // Shift data to right side of slot value = value >> (256 - dataLength * 8); } // treating as value type assembly { sstore(slot, value) } } /// @notice Retrieves a value metadata field from storage function retrieveValueMetadataInfo( address instance, MetadataFields field ) private view returns (bytes32 res) { uint256 metadataSlotNumber; assembly { metadataSlotNumber := _metadata.slot } uint256 slot = uint256(keccak256(abi.encode(instance, metadataSlotNumber))) + uint8(field); assembly { res := sload(slot) } } /// @notice Retrieves a dynamic metadata field from storage function retrieveDynamicMetadataInfo( address instance, MetadataFields field ) private view returns (bytes memory) { uint256 metadataSlotNumber; assembly { metadataSlotNumber := _metadata.slot } uint256 slot = uint256(keccak256(abi.encode(instance, metadataSlotNumber))) + uint8(field); bytes32 slotData; assembly { slotData := sload(slot) } if (slotData >> 255 == 0) { // Remove extra zeros from the data bytes memory returnData; assembly { let dataSize := 0 for { let i := 0 } lt(i, 32) { i := add(i, 1) } { if iszero(shr(mul(i, 8), slotData)) { dataSize := i break } } let freeMem := mload(0x40) mstore(freeMem, dataSize) mstore(add(freeMem, 0x20), shl(sub(256, mul(dataSize, 8)), slotData)) mstore(0x40, add(freeMem, 0x40)) returnData := freeMem } return returnData; } // Retrieve dymanic data from dynamic slot bytes32 firstSlotDynamic; bytes memory res; assembly { res := mload(0x40) firstSlotDynamic := sload(slotData) mstore(add(res, 30), firstSlotDynamic) mstore(0x40, add(res, add(shr(240, firstSlotDynamic), 0x20))) } for (uint256 i = 32; i < res.length; i += 32) { uint256 slotIncrement = i + 30; bytes32 data; assembly { data := sload(add(slotData, i)) mstore(add(res, slotIncrement), data) } } return res; } }
// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.20; import "../tokens/IERC721.sol"; import "./PartyGovernanceNFT.sol"; import "./PartyGovernance.sol"; /// @notice The governance contract that also custodies the precious NFTs. This /// is also the Governance NFT 721 contract. contract Party is PartyGovernanceNFT { // Arguments used to initialize the party. struct PartyOptions { PartyGovernance.GovernanceOpts governance; ProposalStorage.ProposalEngineOpts proposalEngine; string name; string symbol; uint256 customizationPresetId; } // Arguments used to initialize the `PartyGovernanceNFT`. struct PartyInitData { PartyOptions options; IERC721[] preciousTokens; uint256[] preciousTokenIds; address[] authorities; uint40 rageQuitTimestamp; } /// @notice Version ID of the party implementation contract. uint16 public constant VERSION_ID = 1; // Set the `Globals` contract. constructor(IGlobals globals) PartyGovernanceNFT(globals) {} /// @notice Initializer to be delegatecalled by `Proxy` constructor. Will /// revert if called outside the constructor. /// @param initData Options used to initialize the party governance. function initialize(PartyInitData memory initData) external onlyConstructor { PartyGovernanceNFT._initialize( initData.options.name, initData.options.symbol, initData.options.customizationPresetId, initData.options.governance, initData.options.proposalEngine, initData.preciousTokens, initData.preciousTokenIds, initData.authorities, initData.rageQuitTimestamp ); } receive() external payable {} }
// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.20; import { IGlobals } from "../globals/IGlobals.sol"; import { LibGlobals } from "../globals/LibGlobals.sol"; import { IMetadataProvider } from "./IMetadataProvider.sol"; import { Multicall } from "../utils/Multicall.sol"; /// @notice A registry of custom metadata providers for Party Cards. contract MetadataRegistry is Multicall { event ProviderSet(address indexed instance, IMetadataProvider indexed provider); event RegistrarSet(address indexed registrar, address indexed instance, bool canSetData); error NotAuthorized(address caller, address instance); // The `Globals` contract storing global configuration values. This contract // is immutable and it’s address will never change. IGlobals private immutable _GLOBALS; /// @notice Get the metadata provider for a Party instance. mapping(address instance => IMetadataProvider provider) public getProvider; /// @notice Whether or not an address is a registar that can set the /// provider and metadata for another instance. If registrar is set /// true for `address(1)`, the address is a universal registar and /// can set data for any instance. /// @dev Registrars' ability to set metadata for another instance must also be /// supported by the metadata provider used by that instance, indicated by /// `IMetadataProvider.supportsRegistrars()`. mapping(address registrar => mapping(address instance => bool canSetData)) private _isRegistrar; /// @param globals The address of the `Globals` contract. /// @param registrars The addresses of the initial universal registrars. constructor(IGlobals globals, address[] memory registrars) { _GLOBALS = globals; // Set the initial universal registrars. for (uint256 i = 0; i < registrars.length; i++) { _isRegistrar[registrars[i]][address(1)] = true; } } /// @notice Set the metadata provider for a Party instance. /// @param instance The address of the instance. /// @param provider The address of the metadata provider. function setProvider(address instance, IMetadataProvider provider) external { // Check if the caller is authorized to set the provider for the instance. if (!isRegistrar(msg.sender, instance)) revert NotAuthorized(msg.sender, instance); getProvider[instance] = provider; emit ProviderSet(instance, provider); } /// @notice Set whether or not an address can set metadata for a Party instance. /// @param registrar The address of the possible registrar. /// @param instance The address of the instance the registrar can set /// metadata for. /// @param canSetData Whether or not the address can set data for the instance. function setRegistrar(address registrar, address instance, bool canSetData) external { if ( msg.sender != instance && msg.sender != _GLOBALS.getAddress(LibGlobals.GLOBAL_DAO_WALLET) ) { revert NotAuthorized(msg.sender, instance); } _isRegistrar[registrar][instance] = canSetData; emit RegistrarSet(registrar, instance, canSetData); } /// @notice Get whether or not an address can set metadata for a Party instance. /// @param registrar The address of the possible registrar. /// @param instance The address of the instance the registrar can set /// metadata for. /// @return canSetData Whether or not the address can set data for the instance. function isRegistrar(address registrar, address instance) public view returns (bool) { return registrar == instance || _isRegistrar[registrar][address(1)] || _isRegistrar[registrar][instance]; } /// @notice Get the metadata for a Party instance. /// @param instance The address of the instance. /// @param tokenId The ID of the token to get the metadata for. /// @return metadata The encoded metadata. function getMetadata(address instance, uint256 tokenId) external view returns (bytes memory) { IMetadataProvider provider = getProvider[instance]; return address(provider) != address(0) ? provider.getMetadata(instance, tokenId) : bytes(""); } }
// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.20; import { Multicall } from "../utils/Multicall.sol"; import { MetadataRegistry } from "./MetadataRegistry.sol"; import { IMetadataProvider } from "./IMetadataProvider.sol"; import { IGlobals } from "../globals/IGlobals.sol"; import { LibGlobals } from "../globals/LibGlobals.sol"; /// @notice A contract that provides custom metadata for Party Cards. contract MetadataProvider is IMetadataProvider, Multicall { event MetadataSet(address indexed instance, bytes metadata); error NotAuthorized(address caller, address instance); // The `Globals` contract storing global configuration values. This contract // is immutable and it’s address will never change. IGlobals internal immutable _GLOBALS; /// @inheritdoc IMetadataProvider bool public constant supportsRegistrars = true; // The metadata for each Party instance. mapping(address instance => bytes metadata) internal _metadata; // Set the `Globals` contract. constructor(IGlobals globals) { _GLOBALS = globals; } /// @inheritdoc IMetadataProvider function getMetadata( address instance, uint256 ) external view virtual override returns (bytes memory) { return _metadata[instance]; } /// @notice Set the metadata for a Party instance. /// @param instance The address of the instance. /// @param metadata The encoded metadata. function setMetadata(address instance, bytes memory metadata) external virtual { if (instance != msg.sender) { MetadataRegistry registry = MetadataRegistry( _GLOBALS.getAddress(LibGlobals.GLOBAL_METADATA_REGISTRY) ); // Check if the caller is authorized to set metadata for the instance. if (!registry.isRegistrar(msg.sender, instance)) { revert NotAuthorized(msg.sender, instance); } } _metadata[instance] = metadata; emit MetadataSet(instance, metadata); } }
// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.20; import "../utils/Implementation.sol"; // Single registry of global values controlled by multisig. // See `LibGlobals` for all valid keys. interface IGlobals { function multiSig() external view returns (address); function getBytes32(uint256 key) external view returns (bytes32); function getUint256(uint256 key) external view returns (uint256); function getBool(uint256 key) external view returns (bool); function getAddress(uint256 key) external view returns (address); function getImplementation(uint256 key) external view returns (Implementation); function getIncludesBytes32(uint256 key, bytes32 value) external view returns (bool); function getIncludesUint256(uint256 key, uint256 value) external view returns (bool); function getIncludesAddress(uint256 key, address value) external view returns (bool); function setBytes32(uint256 key, bytes32 value) external; function setUint256(uint256 key, uint256 value) external; function setBool(uint256 key, bool value) external; function setAddress(uint256 key, address value) external; function setIncludesBytes32(uint256 key, bytes32 value, bool isIncluded) external; function setIncludesUint256(uint256 key, uint256 value, bool isIncluded) external; function setIncludesAddress(uint256 key, address value, bool isIncluded) external; }
// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.20; // Valid keys in `IGlobals`. Append-only. library LibGlobals { // The Globals commented out below were depreciated in 1.2; factories // can now choose the implementation address to deploy and no longer // deploy the latest implementation. They will no longer be updated // in future releases. // // See https://github.com/PartyDAO/party-migrations for // implementation addresses by release. uint256 internal constant GLOBAL_PARTY_IMPL = 1; uint256 internal constant GLOBAL_PROPOSAL_ENGINE_IMPL = 2; uint256 internal constant GLOBAL_PARTY_FACTORY = 3; uint256 internal constant GLOBAL_GOVERNANCE_NFT_RENDER_IMPL = 4; uint256 internal constant GLOBAL_CF_NFT_RENDER_IMPL = 5; uint256 internal constant GLOBAL_OS_ZORA_AUCTION_TIMEOUT = 6; uint256 internal constant GLOBAL_OS_ZORA_AUCTION_DURATION = 7; // uint256 internal constant GLOBAL_AUCTION_CF_IMPL = 8; // uint256 internal constant GLOBAL_BUY_CF_IMPL = 9; // uint256 internal constant GLOBAL_COLLECTION_BUY_CF_IMPL = 10; uint256 internal constant GLOBAL_DAO_WALLET = 11; uint256 internal constant GLOBAL_TOKEN_DISTRIBUTOR = 12; uint256 internal constant GLOBAL_OPENSEA_CONDUIT_KEY = 13; uint256 internal constant GLOBAL_OPENSEA_ZONE = 14; uint256 internal constant GLOBAL_PROPOSAL_MAX_CANCEL_DURATION = 15; uint256 internal constant GLOBAL_ZORA_MIN_AUCTION_DURATION = 16; uint256 internal constant GLOBAL_ZORA_MAX_AUCTION_DURATION = 17; uint256 internal constant GLOBAL_ZORA_MAX_AUCTION_TIMEOUT = 18; uint256 internal constant GLOBAL_OS_MIN_ORDER_DURATION = 19; uint256 internal constant GLOBAL_OS_MAX_ORDER_DURATION = 20; uint256 internal constant GLOBAL_DISABLE_PARTY_ACTIONS = 21; uint256 internal constant GLOBAL_RENDERER_STORAGE = 22; uint256 internal constant GLOBAL_PROPOSAL_MIN_CANCEL_DURATION = 23; // uint256 internal constant GLOBAL_ROLLING_AUCTION_CF_IMPL = 24; // uint256 internal constant GLOBAL_COLLECTION_BATCH_BUY_CF_IMPL = 25; uint256 internal constant GLOBAL_METADATA_REGISTRY = 26; // uint256 internal constant GLOBAL_CROWDFUND_FACTORY = 27; // uint256 internal constant GLOBAL_INITIAL_ETH_CF_IMPL = 28; // uint256 internal constant GLOBAL_RERAISE_ETH_CF_IMPL = 29; uint256 internal constant GLOBAL_SEAPORT = 30; uint256 internal constant GLOBAL_CONDUIT_CONTROLLER = 31; }
// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.20; import { LibSafeCast } from "../utils/LibSafeCast.sol"; import { LibRawResult } from "../utils/LibRawResult.sol"; import { LibRenderer, Color, ColorType } from "../utils/LibRenderer.sol"; import { Strings } from "../utils/vendor/Strings.sol"; import { Base64 } from "../utils/vendor/Base64.sol"; import { RendererBase } from "./RendererBase.sol"; import { RendererStorage } from "./RendererStorage.sol"; import { MetadataRegistry } from "./MetadataRegistry.sol"; import { IMetadataRegistry1_1 } from "./IMetadataRegistry1_1.sol"; import { Party, PartyGovernance } from "../party/PartyGovernance.sol"; import { PartyGovernanceNFT } from "../party/PartyGovernanceNFT.sol"; import { TokenDistributor } from "../distribution/TokenDistributor.sol"; import { IGlobals } from "../globals/IGlobals.sol"; import { LibGlobals } from "../globals/LibGlobals.sol"; import { IFont } from "./fonts/IFont.sol"; contract PartyNFTRenderer is RendererBase { using LibSafeCast for uint256; using LibRawResult for bytes; using Strings for uint256; using Strings for address; error InvalidTokenIdError(); error ExternalURLTooLarge(); // The crowdfund type used to create this party. enum CrowdfundType { // Party was created using flexible crowdfund configuration which // allowed members to contribute an amount in range and variable voting // power awarded in proportion to the amount contributed (e.g. 1 ETH // contributed with 10 ETH total contributed resulting in 10% voting // power for contribution). Flexible, // Party was created using fixed crowdfund configuration which awarded // members with fixed voting power per membership minted with each mint // costing a fixed amount (e.g. 1 ETH for 10% voting power per mint, // total of 10 mints available). Fixed } enum RenderingMethod { ENUM_OFFSET, // Apply an enum offset so that first valid value is 1 FlexibleCrowdfund, FixedCrowdfund } struct ProposalData { uint256 id; string status; } struct Metadata { string name; string description; string externalURL; string image; string banner; string animationURL; string collectionName; string collectionDescription; string collectionExternalURL; address royaltyReceiver; uint256 royaltyAmount; RenderingMethod renderingMethod; } address immutable IMPL; uint256 constant PARTY_CARD_DATA = 1; address constant PARTYSTAR_PARTY_ADDRESS = 0x118928CCAc2035B578ae2D35FBFc2c120B6c4B82; address constant PARTYSTAR_CROWDFUND_ADDRESS = 0x0Bf08f7b6474C2aCCB9b9e325acb6FbcC682dE82; IMetadataRegistry1_1 constant OLD_METADATA_REGISTRY = IMetadataRegistry1_1(0x175487875F0318EdbAB54BBA442fF53b36e96015); /// @notice The old token distributor contract address. address immutable OLD_TOKEN_DISTRIBUTOR; /// @notice The base url for external URLs. External URL is BASE_EXTERNAL_URL + PARTY_ADDRESS /// @dev First byte is the size of the data, the rest is the data (starting from MSB) bytes32 private immutable BASE_EXTERNAL_URL_DATA; constructor( IGlobals globals, RendererStorage rendererStorage, IFont font, address oldTokenDistributor, string memory baseExternalURL ) RendererBase(globals, rendererStorage, font) { IMPL = address(this); OLD_TOKEN_DISTRIBUTOR = oldTokenDistributor; bytes memory baseExternalURLBytes = bytes(baseExternalURL); if (baseExternalURLBytes.length > 31) { // Must be less than or equal to 31 bytes because 1 byte is used for the length. revert ExternalURLTooLarge(); } bytes32 baseExternalUrlData = bytes32(baseExternalURLBytes.length) << 248; baseExternalUrlData |= bytes32(baseExternalURLBytes) >> 8; BASE_EXTERNAL_URL_DATA = baseExternalUrlData; } function royaltyInfo( uint256, uint256 ) external view returns (address receiver, uint256 royaltyAmount) { // Get any custom metadata for this party. Metadata memory metadata = getCustomMetadata(0); // By default, there are no royalties. return (metadata.royaltyReceiver, metadata.royaltyAmount); } function contractURI() external view override returns (string memory) { (bool isDarkMode, Color color) = getCustomizationChoices(); (string memory image, string memory banner) = LibRenderer.getCollectionImageAndBanner( color, isDarkMode ); // Get any custom metadata for this party. Metadata memory metadata = getCustomMetadata(0); return string.concat( "data:application/json;base64,", Base64.encode( abi.encodePacked( '{"name":"', bytes(metadata.collectionName).length == 0 ? generateCollectionName() : metadata.collectionName, '", "description":"', bytes(metadata.collectionDescription).length == 0 ? generateCollectionDescription() : metadata.collectionDescription, '", "external_url":"', bytes(metadata.collectionExternalURL).length == 0 ? generateExternalURL() : metadata.collectionExternalURL, '", "image":"', bytes(metadata.image).length == 0 ? image : metadata.image, // Determine which banner to render. bytes(metadata.banner).length == 0 ? bytes(metadata.image).length == 0 // No custom banner and no custom image, use the default Party banner. ? string.concat('", "banner":"', banner) // No custom banner but custom image, do not include banner in metadata. : "" : string.concat('", "banner":"', metadata.banner), // Custom banner, use it. '"}' ) ) ); } function tokenURI(uint256 tokenId) external view returns (string memory) { if (PartyGovernanceNFT(address(this)).ownerOf(tokenId) == address(0)) { revert InvalidTokenIdError(); } // Add backward compatibility for rendering custom metadata for // Partystar party using old `MetadataRegistry` contract. if (address(this) == PARTYSTAR_PARTY_ADDRESS) { ( string memory customName, string memory customDescription, string memory customImage ) = OLD_METADATA_REGISTRY.customPartyMetadataByCrowdfund(PARTYSTAR_CROWDFUND_ADDRESS); return string.concat( "data:application/json;base64,", Base64.encode( abi.encodePacked( '{"name":"', string.concat(customName, " #", tokenId.toString()), '", "description":"', customDescription, '", "external_url":"', generateExternalURL(), '", "attributes": [', generateAttributes(tokenId), '], "image":"', customImage, '"}' ) ) ); } string memory image = generateSVG( PartyGovernanceNFT(address(this)).name(), generateVotingPowerPercentage(tokenId), getLatestProposalStatuses(), PartyGovernance(address(this)).lastProposalId(), tokenId ); // Get any custom metadata for this party. Metadata memory metadata = getCustomMetadata(tokenId); // Construct metadata. return string.concat( "data:application/json;base64,", Base64.encode( abi.encodePacked( '{"name":"', generateName( bytes(metadata.name).length == 0 ? PartyGovernanceNFT(address(this)).name() : metadata.name, tokenId, metadata.renderingMethod ), '", "description":"', bytes(metadata.description).length == 0 ? generateDescription(PartyGovernanceNFT(address(this)).name(), tokenId) : string.concat( metadata.description, " ", // Append default description. generateDescription( PartyGovernanceNFT(address(this)).name(), tokenId ) ), '", "external_url":"', bytes(metadata.externalURL).length == 0 ? generateExternalURL() : metadata.externalURL, bytes(metadata.animationURL).length == 0 ? "" : string.concat('", "animation_url":"', metadata.animationURL), '", "party_card_url":"', // Custom metadata field. image, '", "image":"', bytes(metadata.image).length == 0 ? image : metadata.image, hasPartyStarted() ? string.concat('", "attributes": [', generateAttributes(tokenId), "]") : '"', "}" ) ) ); } function generateName( string memory partyName, uint256 tokenId, RenderingMethod renderingMethod ) private view returns (string memory) { if ( renderingMethod == RenderingMethod.FixedCrowdfund || (renderingMethod == RenderingMethod.ENUM_OFFSET && getCrowdfundType() == CrowdfundType.Fixed) ) { return string.concat(partyName, " #", tokenId.toString()); } else { if (hasPartyStarted()) { return string.concat(generateVotingPowerPercentage(tokenId), "% Voting Power"); } else { return "Party Membership"; } } } function generateExternalURL() private view returns (string memory) { bytes32 baseExternalUrlData = BASE_EXTERNAL_URL_DATA; uint8 externalUrlLength = uint8(uint256(baseExternalUrlData >> 248)); string memory baseExternalUrl; assembly { let freeMem := mload(0x40) baseExternalUrl := freeMem // Store the length of the data mstore(baseExternalUrl, externalUrlLength) // Store the data to memory mstore(add(baseExternalUrl, 0x20), shl(8, baseExternalUrlData)) // Update free mem mstore(0x40, add(freeMem, 0x40)) } return string.concat(baseExternalUrl, address(this).toHexString()); } function generateDescription( string memory partyName, uint256 tokenId ) private view returns (string memory) { if (hasPartyStarted()) { return string.concat( "This membership represents ", generateVotingPowerPercentage(tokenId), "% voting power in ", partyName, ". Head to ", generateExternalURL(), " to view the Party's latest activity." ); } else { return string.concat( "This item represents membership in ", partyName, ". Exact voting power will be determined when the crowdfund ends. Head to ", generateExternalURL(), " to view the Party's latest activity." ); } } function generateAttributes(uint256 tokenId) private view returns (string memory) { string memory votingPowerPercentage = generateVotingPowerPercentage(tokenId); if ( keccak256(abi.encodePacked(votingPowerPercentage)) == keccak256(abi.encodePacked("--")) ) { votingPowerPercentage = "0"; } return string.concat( '{"trait_type":"Voting Power", "value": "', votingPowerPercentage, '", "max_value":100}' ); } function generateCollectionName() internal view returns (string memory) { return string.concat("Party Memberships: ", PartyGovernanceNFT(address(this)).name()); } function generateCollectionDescription() internal view returns (string memory) { return string.concat( "This collection represents memberships in the following Party: ", PartyGovernanceNFT(address(this)).name(), ". Head to ", generateExternalURL(), " to view the Party's latest activity." ); } function generateSVG( string memory partyName, string memory votingPowerPercentage, PartyGovernance.ProposalStatus[4] memory proposalStatuses, uint256 latestProposalId, uint256 tokenId ) public view returns (string memory) { // Get the customization data for this party. (bool isDarkMode, Color color) = getCustomizationChoices(); return generateSVG( partyName, votingPowerPercentage, proposalStatuses, latestProposalId, tokenId, color, isDarkMode ); } function generateSVG( string memory partyName, string memory votingPowerPercentage, PartyGovernance.ProposalStatus[4] memory proposalStatuses, uint256 latestProposalId, uint256 tokenId, Color color, bool isDarkMode ) public view returns (string memory) { return string.concat( "data:image/svg+xml;base64,", Base64.encode( abi.encodePacked( // Split to avoid stack too deep errors generateSVG1(color, isDarkMode), generateSVG2(partyName, color), generateSVG3(partyName, color), generateSVG4(latestProposalId, proposalStatuses), generateSVG5(votingPowerPercentage, color), generateSVG6(tokenId, color) ) ) ); } function generateSVG1(Color color, bool isDarkMode) private pure returns (string memory) { return string.concat( '<svg width="540" height="540" viewBox="0 -10 360 560" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><linearGradient id="d" x1="0" x2="0" y1="0" y2="1"><stop offset="0" stop-color="', isDarkMode ? "#2e3847" : "#ffffff", '"/><stop offset="1" stop-color="', isDarkMode ? "#000000" : "#e6edf5", '"/></linearGradient><linearGradient id="e" x1="0" x2="0" y1="0" y2="1"><stop offset="0" stop-color="', isDarkMode ? "#8091a8" : "#e6edf6", '"/><stop offset="1" stop-color="', isDarkMode ? "#2e3848" : "#bccbdd", '"/></linearGradient><linearGradient id="f" x1="0" x2="0" y1="1" y2="0"><stop offset="0" stop-color="', LibRenderer.generateColorHex(color, ColorType.SECONDARY), '"/><stop offset="1" stop-color="', LibRenderer.generateColorHex(color, ColorType.PRIMARY), '"/></linearGradient><linearGradient id="f2" x1="0" x2="0" y1="-.5" y2="1"><stop offset="0" stop-color="', LibRenderer.generateColorHex(color, ColorType.SECONDARY), '"/><stop offset="1" stop-color="', LibRenderer.generateColorHex(color, ColorType.PRIMARY), '"/></linearGradient><linearGradient id="h" x1="0" x2="0" y1="0" y2="1"><stop offset="0" stop-color="', isDarkMode ? "#ffffff" : "#3f485f", '"/><stop offset=".5" stop-color="', isDarkMode ? "#a7b8cf" : "#000000", '"/></linearGradient><radialGradient cx="1" cy="-.5" id="i" r="2"><stop offset="0" stop-color="#dce5f0"/><stop offset=".5" stop-color="#dce5f0" stop-opacity="0"/></radialGradient>' ); } function generateSVG2( string memory partyName, Color color ) private view returns (string memory) { (uint256 duration, uint256 steps, uint256 delay, uint256 translateX) = LibRenderer .calcAnimationVariables(partyName); return string.concat( '<symbol id="a" viewBox="0 0 300.15 300"><path d="M6.07 0v300m-3-300v300M.07 0v300m9-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300m3-300v300" style="stroke-width:.15px;stroke:', LibRenderer.generateColorHex(color, ColorType.PRIMARY), ';fill:none;0"/></symbol><style>.z{animation:x ', duration.toString(), "s steps(", steps.toString(), ") infinite;}.y{animation-delay:", delay.toString(), "s}@keyframes x{to{transform:translateX(-", translateX.toString(), "px)}}.v{fill:", LibRenderer.generateColorHex(color, ColorType.DARK), ";font-family:pixeldroidConsoleRegular,Console;font-size:48px}.w{animation:W 1s steps(1, jump-end) infinite;}@keyframes W{50%{fill:", LibRenderer.generateColorHex(color, ColorType.LIGHT), "}}@font-face{font-family:pixeldroidConsoleRegular;src:url(", _font.getFont(), ");}</style></defs>" ); } function generateSVG3( string memory partyName, Color color ) private pure returns (string memory) { return string.concat( '<rect height="539" rx="29.5" ry="29.5" style="fill:url(#d);stroke:url(#e)" width="359" x=".5" y=".5"/><rect rx="15.5" ry="15.5" style="stroke:url(#f);fill:', LibRenderer.generateColorHex(color, ColorType.PRIMARY), '" width="331" height="346" x="14.5" y="179.5"/><path d="M321 501H198v-27h123v27Zm9-282H30v27h300v-27Zm0 60H30v27h300v-27Zm0 30H30v27h300v-27Zm0 30H30v27h300v-27Zm0 30H30v27h300v-27Z" style="fill:', LibRenderer.generateColorHex(color, ColorType.LIGHT), ';"/><clipPath id="clip"><path d="M31 501H198v-27h123v27Zm9-282H30v27h300v-27Zm0"/></clipPath><g clip-path="url(#clip)"><g class="z"><text class="v" x="327" y="240">', partyName, '</text></g><g class="z y"><text class="v" x="327" y="240">', partyName, "</text></g></g>" ); } function generateSVG4( uint256 latestProposalId, PartyGovernance.ProposalStatus[4] memory proposalStatuses ) private pure returns (string memory part) { // Render latest 4 proposals, or up to the latest proposal if there are // less than 4 proposals. for (uint256 i; i < (latestProposalId < 4 ? latestProposalId : 4); i++) { // Should produce something like this: // '<text class="v" x="30" y="300">', // latestProposalStatus1, // '</text><text class="v" x="30" y="330">', // latestProposalStatus2, // '</text><text class="v" x="30" y="360">', // latestProposalStatus3, // '</text><text class="v" x="30" y="390">', // latestProposalStatus4, // '</text><text class="v" x="201" y="495">' part = string( abi.encodePacked( part, '<text class="v" x="30" y="', (300 + (i * 30)).toString(), '">', generateProposalStatus(latestProposalId - i, proposalStatuses[i]), "</text>" ) ); } part = string(abi.encodePacked(part, '<text class="v" x="201" y="495">')); } function generateSVG5( string memory votingPowerPercentage, Color color ) private pure returns (string memory) { return string.concat( votingPowerPercentage, '</text><text class="v" x="297" y="495">%</text>' '<use height="300" x="30" y="210" width="300.15" xlink:href="#a"/><use height="300" transform="rotate(-90 270 240)" width="300.15" xlink:href="#a"/><rect rx="3.5" ry="3.5" style="fill:none;stroke:', LibRenderer.generateColorHex(color, ColorType.DARK), ';stroke-width:3px" width="138" height="42" x="190.5" y="466.5"/><path fill="', LibRenderer.generateColorHex(color, ColorType.DARK) ); } function generateSVG6(uint256 tokenId, Color color) private view returns (string memory) { return string.concat( '"', _storage.readFile(PARTY_CARD_DATA), '<path d="M285 164c-7.72 0-14-6.28-14-14s6.28-14 14-14h45c7.72 0 14 6.28 14 14s-6.28 14-14 14h-45Z" style="fill:none;stroke:', LibRenderer.generateColorHex(color, ColorType.PRIMARY), ';stroke-width:2px"/><path d="M307.5 68.19c-20.71 0-37.5 11.6-37.5 25.91s16.79 25.91 37.5 25.91S345 108.41 345 94.1s-16.79-25.91-37.5-25.91Zm12.84 41.66v-7.31l9.74-3.17-25.51-8.29 14.01 19.28a43.35 43.35 0 0 1-20.64.38v-8.33l9.74-3.17-25.51-8.29 14.06 19.35c-10.92-3-18.63-10.23-18.63-18.68 0-6.22 4.17-11.78 10.73-15.48l14.24 19.6V85.5l9.74-3.17-22.07-7.17a40.24 40.24 0 0 1 17.25-3.7c11.3 0 21.13 4.23 26.21 10.47l-23.17-7.53 15.76 21.7V85.86l8.32-2.7a14.9 14.9 0 0 1 2.77 8.48c0 8.04-6.97 14.98-17.05 18.22Z" style="fill:#A7B8CF"/><clipPath id="C"><path d="m98.43 483.54 1.39 5.02h-3.77l1.42-5.02.38-2.02h.17l.41 2.02ZM171 470v35a5 5 0 0 1-5 5H35a5 5 0 0 1-5-5v-35a5 5 0 0 1 5-5h131a5 5 0 0 1 5 5Zm-93.49 10.56c-1.71-1.4-3.1-2.06-5.5-2.06-3.82 0-7.39 3.19-7.39 8.64 0 6.35 3.17 9.36 7.34 9.36 2.4 0 4.12-.82 5.47-2.47v-4.06c-1.06 1.58-2.35 2.95-4.54 2.95-3.19 0-4.13-3.44-4.13-5.38 0-3.42 1.76-4.94 4.1-4.94 1.78 0 3.26.71 4.63 2.93v-4.97Zm11.28 11.81h-4.36v-13.54h-4.25v17.3h8.61v-3.77Zm17.27 3.77-5.06-17.3h-6.1l-5.09 17.3h4.27l1.06-4.06h5.59l1.06 4.06h4.27Zm6.21-17.3H108v17.3h4.27v-17.3Zm22.1 0h-6.39l-2.45 9.73-.28 3.64h-.24l-.32-3.67-2.46-9.71h-6.39v17.3h4.15v-10.44c0-.38-.1-2.16-.17-3.07h.24l3.07 13.51h3.96l3.07-13.51h.24l-.19 3.07v10.44h4.15v-17.3Z" /></clipPath><g clip-path="url(#C)"><rect class="w" x="30" y="465" width="142" height="50" fill="', hasUnclaimedDistribution(tokenId) ? LibRenderer.generateColorHex(color, ColorType.DARK) : LibRenderer.generateColorHex(color, ColorType.LIGHT), '"/></g><rect height="345" rx="15" ry="15" style="fill:url(#i)" width="330" x="15" y="180"/><text text-anchor="middle" style="font-family:ui-monospace,Cascadia Mono,Menlo,Monaco,Segoe UI Mono,Roboto Mono,Oxygen Mono,Ubuntu Monospace,Source Code Pro,Droid Sans Mono,Fira Mono,Courier,monospace;fill:', LibRenderer.generateColorHex(color, ColorType.PRIMARY), ';font-weight:500;" x="307.5" y="156">', // Always render token ID with 3 digits. LibRenderer.prependNumWithZeros(tokenId.toString(), 3), "</text></svg>" ); } function generateVotingPowerPercentage(uint256 tokenId) private view returns (string memory) { Party party = Party(payable(address(this))); uint256 totalVotingPower = getTotalVotingPower(); if (totalVotingPower == 0) { return "--"; } uint256 intrinsicVotingPowerPercentage = (party.votingPowerByTokenId(tokenId) * 1e18) / totalVotingPower; if (intrinsicVotingPowerPercentage == 1e18) { return "100"; } else if (intrinsicVotingPowerPercentage < 0.1e18) { return LibRenderer.formatAsDecimalString(intrinsicVotingPowerPercentage, 16, 3); } else { return LibRenderer.formatAsDecimalString(intrinsicVotingPowerPercentage, 16, 4); } } function generateProposalStatus( uint256 proposalId, PartyGovernance.ProposalStatus status ) private pure returns (string memory) { string memory statusMessage; if (status == PartyGovernance.ProposalStatus.Voting) { statusMessage = "Voting now"; } else if (status == PartyGovernance.ProposalStatus.Passed) { statusMessage = "Passing"; } else if (status == PartyGovernance.ProposalStatus.Ready) { statusMessage = "Executable"; } else if (status == PartyGovernance.ProposalStatus.InProgress) { statusMessage = "In progress"; } else if (status == PartyGovernance.ProposalStatus.Complete) { statusMessage = "Complete"; } else if (status == PartyGovernance.ProposalStatus.Defeated) { statusMessage = "Defeated"; } else if (status == PartyGovernance.ProposalStatus.Cancelled) { statusMessage = "Cancelled"; } else { return ""; } return string.concat("#", proposalId.toString(), " - ", statusMessage); } function getCustomMetadata(uint256 tokenId) private view returns (Metadata memory metadata) { MetadataRegistry registry = MetadataRegistry( _GLOBALS.getAddress(LibGlobals.GLOBAL_METADATA_REGISTRY) ); bytes memory encodedMetadata = registry.getMetadata(address(this), tokenId); return encodedMetadata.length != 0 ? abi.decode(encodedMetadata, (Metadata)) : metadata; } function getLatestProposalStatuses() private view returns (PartyGovernance.ProposalStatus[4] memory proposalStatuses) { uint256 latestProposalId = PartyGovernance(address(this)).lastProposalId(); uint256 numOfProposalsToDisplay = latestProposalId < 4 ? latestProposalId : 4; for (uint256 i; i < numOfProposalsToDisplay; ++i) { uint256 proposalId = latestProposalId - i; PartyGovernance.ProposalStatus proposalStatus; // Get the status of the proposal, regardless of the version of the // Party contract. (bool s, bytes memory r) = address(this).staticcall( abi.encodeCall(PartyGovernance.getProposalStateInfo, (proposalId)) ); if (!s) { r.rawRevert(); } assembly { proposalStatus := mload(add(r, 0x20)) } proposalStatuses[i] = proposalStatus; } } function getCrowdfundType() private view returns (CrowdfundType crowdfundType) { Party party = Party(payable(address(this))); uint256 numOfTokensToCheck = 5; uint256 tokenCount = party.tokenCount(); if (tokenCount < numOfTokensToCheck) { // Default to flexible membership. return CrowdfundType.Flexible; } else { // Check the voting power of several tokens. If they are all the // same, assume it is a fixed membership. uint256 lastTokenId = tokenCount / numOfTokensToCheck; for (uint256 i = 2; i <= numOfTokensToCheck; ++i) { uint256 tokenId = i * (tokenCount / numOfTokensToCheck); if ( party.votingPowerByTokenId(tokenId) != party.votingPowerByTokenId(lastTokenId) ) { return CrowdfundType.Flexible; } lastTokenId = tokenId; } return CrowdfundType.Fixed; } } function getTotalVotingPower() private view returns (uint96 totalVotingPower) { // Get the total voting power of the Party regardless of // the version of the Party contract. (bool s, bytes memory r) = address(this).staticcall( abi.encodeCall(PartyGovernance.getGovernanceValues, ()) ); if (!s) { r.rawRevert(); } assembly { totalVotingPower := mload(add(r, 0x80)) } } function hasUnclaimedDistribution(uint256 tokenId) private view returns (bool) { if (address(this) == IMPL) return false; // There will only be one distributor if old token distributor is not set TokenDistributor[] memory distributors = new TokenDistributor[](1); if (OLD_TOKEN_DISTRIBUTOR != address(0)) { distributors = new TokenDistributor[](2); distributors[1] = TokenDistributor(OLD_TOKEN_DISTRIBUTOR); } distributors[0] = TokenDistributor( _GLOBALS.getAddress(LibGlobals.GLOBAL_TOKEN_DISTRIBUTOR) ); Party party = Party(payable(address(this))); for (uint256 i; i < distributors.length; ++i) { TokenDistributor distributor = distributors[i]; uint256 lastDistributionId = distributor.lastDistributionIdPerParty(party); for ( uint256 distributionId = 1; distributionId <= lastDistributionId; ++distributionId ) { if (!distributor.hasPartyTokenIdClaimed(party, tokenId, distributionId)) { return true; } } } return false; } function hasPartyStarted() private view returns (bool) { return getTotalVotingPower() != 0; } }
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8; // Minimal ERC721 interface. interface IERC721 { event Transfer(address indexed owner, address indexed to, uint256 indexed tokenId); event Approval(address indexed owner, address indexed operator, uint256 indexed tokenId); event ApprovalForAll(address indexed owner, address indexed operator, bool approved); function transferFrom(address from, address to, uint256 tokenId) external; function safeTransferFrom( address from, address to, uint256 tokenId, bytes calldata data ) external; function safeTransferFrom(address from, address to, uint256 tokenId) external; function approve(address operator, uint256 tokenId) external; function setApprovalForAll(address operator, bool isApproved) external; function name() external view returns (string memory); function symbol() external view returns (string memory); function getApproved(uint256 tokenId) external view returns (address); function isApprovedForAll(address owner, address operator) external view returns (bool); function ownerOf(uint256 tokenId) external view returns (address); function balanceOf(address owner) external view returns (uint256); }
// SPDX-License-Identifier: GPL-3.0 pragma solidity 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`. contract PartyGovernanceNFT is PartyGovernance, ERC721, IERC2981 { using LibSafeCast for uint256; using LibSafeCast for uint96; using LibERC20Compat for IERC20; using LibAddress for address payable; error OnlyAuthorityError(); error OnlySelfError(); error UnauthorizedToBurnError(); error FixedRageQuitTimestampError(uint40 rageQuitTimestamp); error CannotRageQuitError(uint40 rageQuitTimestamp); error CannotDisableRageQuitAfterInitializationError(); error InvalidTokenOrderError(); error BelowMinWithdrawAmountError(uint256 amount, uint256 minAmount); error NothingToBurnError(); event AuthorityAdded(address indexed authority); event AuthorityRemoved(address indexed authority); event RageQuitSet(uint40 oldRageQuitTimestamp, uint40 newRageQuitTimestamp); event Burn(address caller, uint256 tokenId, uint256 votingPower); event RageQuit(address caller, uint256[] tokenIds, IERC20[] withdrawTokens, address receiver); event PartyCardIntrinsicVotingPowerSet(uint256 indexed tokenId, uint256 intrinsicVotingPower); uint40 private constant ENABLE_RAGEQUIT_PERMANENTLY = 0x6b5b567bfe; // uint40(uint256(keccak256("ENABLE_RAGEQUIT_PERMANENTLY"))) uint40 private constant DISABLE_RAGEQUIT_PERMANENTLY = 0xab2cb21860; // uint40(uint256(keccak256("DISABLE_RAGEQUIT_PERMANENTLY"))) // Token address used to indicate ETH. address private constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; // The `Globals` contract storing global configuration values. This contract // is immutable and its address will never change. IGlobals private immutable _GLOBALS; /// @notice The number of tokens that have been minted. uint96 public tokenCount; /// @notice The total minted voting power. /// Capped to `_governanceValues.totalVotingPower` unless minting /// party cards for initial crowdfund. uint96 public mintedVotingPower; /// @notice The timestamp until which ragequit is enabled. Can be set to the /// `ENABLE_RAGEQUIT_PERMANENTLY`/`DISABLE_RAGEQUIT_PERMANENTLY` /// values to enable/disable ragequit permanently. /// `DISABLE_RAGEQUIT_PERMANENTLY` can only be set during /// initialization. uint40 public rageQuitTimestamp; /// @notice The voting power of `tokenId`. mapping(uint256 => uint256) public votingPowerByTokenId; /// @notice Address with authority to mint cards and update voting power for the party. mapping(address => bool) public isAuthority; modifier onlyAuthority() { if (!isAuthority[msg.sender]) { revert OnlyAuthorityError(); } _; } modifier onlySelf() { if (msg.sender != address(this)) { revert OnlySelfError(); } _; } // Set the `Globals` contract. The name or symbol of ERC721 does not matter; // it will be set in `_initialize()`. constructor(IGlobals globals) payable PartyGovernance(globals) ERC721("", "") { _GLOBALS = globals; } // Initialize storage for proxy contracts. function _initialize( string memory name_, string memory symbol_, uint256 customizationPresetId, PartyGovernance.GovernanceOpts memory governanceOpts, ProposalStorage.ProposalEngineOpts memory proposalEngineOpts, IERC721[] memory preciousTokens, uint256[] memory preciousTokenIds, address[] memory authorities, uint40 rageQuitTimestamp_ ) internal { PartyGovernance._initialize( governanceOpts, proposalEngineOpts, preciousTokens, preciousTokenIds ); name = name_; symbol = symbol_; rageQuitTimestamp = rageQuitTimestamp_; unchecked { for (uint256 i; i < authorities.length; ++i) { isAuthority[authorities[i]] = true; } } if (customizationPresetId != 0) { RendererStorage(_GLOBALS.getAddress(LibGlobals.GLOBAL_RENDERER_STORAGE)) .useCustomizationPreset(customizationPresetId); } } /// @inheritdoc ERC721 function ownerOf(uint256 tokenId) public view override returns (address owner) { return ERC721.ownerOf(tokenId); } /// @inheritdoc EIP165 function supportsInterface( bytes4 interfaceId ) public pure override(PartyGovernance, ERC721, IERC165) returns (bool) { return PartyGovernance.supportsInterface(interfaceId) || ERC721.supportsInterface(interfaceId) || interfaceId == type(IERC2981).interfaceId; } /// @inheritdoc ERC721 function tokenURI(uint256) public view override returns (string memory) { _delegateToRenderer(); return ""; // Just to make the compiler happy. } /// @notice Returns a URI for the storefront-level metadata for your contract. function contractURI() external view returns (string memory) { _delegateToRenderer(); return ""; // Just to make the compiler happy. } /// @notice Called with the sale price to determine how much royalty // is owed and to whom. function royaltyInfo(uint256, uint256) external view returns (address, uint256) { _delegateToRenderer(); return (address(0), 0); // Just to make the compiler happy. } /// @notice Return the distribution share amount of a token. Included as an alias /// for `votePowerByTokenId` for backwards compatibility with old /// `TokenDistributor` implementations. /// @param tokenId The token ID to query. /// @return share The distribution shares of `tokenId`. function getDistributionShareOf(uint256 tokenId) public view returns (uint256) { return votingPowerByTokenId[tokenId]; } /// @notice Return the voting power share of a token. Denominated /// fractions of 1e18. I.e., 1e18 = 100%. /// @param tokenId The token ID to query. /// @return share The voting power percentage of `tokenId`. function getVotingPowerShareOf(uint256 tokenId) public view returns (uint256) { uint256 totalVotingPower = _governanceValues.totalVotingPower; return totalVotingPower == 0 ? 0 : (votingPowerByTokenId[tokenId] * 1e18) / totalVotingPower; } /// @notice Mint a governance NFT for `owner` with `votingPower` and /// immediately delegate voting power to `delegate.` Only callable /// by an authority. /// @param owner The owner of the NFT. /// @param votingPower The voting power of the NFT. /// @param delegate The address to delegate voting power to. function mint( address owner, uint256 votingPower, address delegate ) external onlyAuthority returns (uint256 tokenId) { uint96 mintedVotingPower_ = mintedVotingPower; uint96 totalVotingPower = _governanceValues.totalVotingPower; // Cap voting power to remaining unminted voting power supply. uint96 votingPower_ = votingPower.safeCastUint256ToUint96(); // Allow minting past total voting power if minting party cards for // initial crowdfund when there is no total voting power. if (totalVotingPower != 0 && totalVotingPower - mintedVotingPower_ < votingPower_) { unchecked { votingPower_ = totalVotingPower - mintedVotingPower_; } } // Update state. unchecked { tokenId = ++tokenCount; } mintedVotingPower += votingPower_; votingPowerByTokenId[tokenId] = votingPower_; 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. function addVotingPower(uint256 tokenId, uint256 votingPower) external onlyAuthority { uint96 mintedVotingPower_ = mintedVotingPower; uint96 totalVotingPower = _governanceValues.totalVotingPower; // Cap voting power to remaining unminted voting power supply. uint96 votingPower_ = votingPower.safeCastUint256ToUint96(); // Allow minting past total voting power if minting party cards for // initial crowdfund when there is no total voting power. if (totalVotingPower != 0 && totalVotingPower - mintedVotingPower_ < votingPower_) { unchecked { votingPower_ = totalVotingPower - mintedVotingPower_; } } // Update state. mintedVotingPower += votingPower_; uint256 newIntrinsicVotingPower = votingPowerByTokenId[tokenId] + votingPower_; votingPowerByTokenId[tokenId] = newIntrinsicVotingPower; emit PartyCardIntrinsicVotingPowerSet(tokenId, newIntrinsicVotingPower); _adjustVotingPower(ownerOf(tokenId), votingPower_.safeCastUint96ToInt192(), address(0)); } /// @notice Update the total voting power of the party. Only callable by /// an authority. /// @param newVotingPower The new total voting power to add. function increaseTotalVotingPower(uint96 newVotingPower) external onlyAuthority { _governanceValues.totalVotingPower += newVotingPower; } /// @notice Burn governance NFTs and remove their voting power. Can only /// be called by an authority before the party has started. /// @param tokenIds The IDs of the governance NFTs to burn. function burn(uint256[] memory tokenIds) public onlyAuthority { // Authority needs to be able to burn cards during the initial // crowdfund to process refunds but not after the party has started. if (_governanceValues.totalVotingPower != 0) revert UnauthorizedToBurnError(); // Used to update voting power state of party at the end. _burnAndUpdateVotingPower(tokenIds, false); } function _burnAndUpdateVotingPower( uint256[] memory tokenIds, bool checkIfAuthorizedToBurn ) private returns (uint96 totalVotingPowerBurned) { for (uint256 i; i < tokenIds.length; ++i) { uint256 tokenId = tokenIds[i]; // Check if caller is authorized to burn the token. address owner = ownerOf(tokenId); if (checkIfAuthorizedToBurn) { if ( msg.sender != owner && getApproved[tokenId] != msg.sender && !isApprovedForAll[owner][msg.sender] ) { revert UnauthorizedToBurnError(); } } // Must be retrieved before updating voting power for token to be burned. uint96 votingPower = votingPowerByTokenId[tokenId].safeCastUint256ToUint96(); totalVotingPowerBurned += votingPower; // Update voting power for token to be burned. delete votingPowerByTokenId[tokenId]; 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; } /// @notice Burn governance NFT and remove its voting power. Can only be /// called by an authority before the party has started. /// @param tokenId The ID of the governance NFTs to burn. function burn(uint256 tokenId) external { uint256[] memory tokenIds = new uint256[](1); tokenIds[0] = tokenId; burn(tokenIds); } /// @notice Set the timestamp until which ragequit is enabled. /// @param newRageQuitTimestamp The new ragequit timestamp. function setRageQuit(uint40 newRageQuitTimestamp) external onlyHost { // Prevent disabling ragequit after initialization. if (newRageQuitTimestamp == DISABLE_RAGEQUIT_PERMANENTLY) { revert CannotDisableRageQuitAfterInitializationError(); } uint40 oldRageQuitTimestamp = rageQuitTimestamp; // Prevent setting timestamp if it is permanently enabled/disabled. if ( oldRageQuitTimestamp == ENABLE_RAGEQUIT_PERMANENTLY || oldRageQuitTimestamp == DISABLE_RAGEQUIT_PERMANENTLY ) { revert FixedRageQuitTimestampError(oldRageQuitTimestamp); } emit RageQuitSet(oldRageQuitTimestamp, rageQuitTimestamp = newRageQuitTimestamp); } /// @notice Burn a governance NFT and withdraw a fair share of fungible tokens from the party. /// @param tokenIds The IDs of the governance NFTs to burn. /// @param withdrawTokens The fungible tokens to withdraw. Specify the /// `ETH_ADDRESS` value to withdraw ETH. /// @param minWithdrawAmounts The minimum amount of to withdraw for each token. /// @param receiver The address to receive the withdrawn tokens. function rageQuit( uint256[] calldata tokenIds, IERC20[] calldata withdrawTokens, uint256[] calldata minWithdrawAmounts, address receiver ) external { if (tokenIds.length == 0) revert NothingToBurnError(); // Check if ragequit is allowed. uint40 currentRageQuitTimestamp = rageQuitTimestamp; if (currentRageQuitTimestamp != ENABLE_RAGEQUIT_PERMANENTLY) { if ( currentRageQuitTimestamp == DISABLE_RAGEQUIT_PERMANENTLY || currentRageQuitTimestamp < block.timestamp ) { revert CannotRageQuitError(currentRageQuitTimestamp); } } // Used as a reentrancy guard. Will be updated back after ragequit. rageQuitTimestamp = DISABLE_RAGEQUIT_PERMANENTLY; // Update last rage quit timestamp. lastRageQuitTimestamp = uint40(block.timestamp); // Sum up total amount of each token to withdraw. uint256[] memory withdrawAmounts = new uint256[](withdrawTokens.length); { IERC20 prevToken; for (uint256 i; i < withdrawTokens.length; ++i) { IERC20 token = withdrawTokens[i]; // Check if order of tokens to transfer is valid. // Prevent null and duplicate transfers. if (prevToken >= token) revert InvalidTokenOrderError(); prevToken = token; // Check token's balance. uint256 balance = address(token) == ETH_ADDRESS ? address(this).balance : token.balanceOf(address(this)); // Add fair share of tokens from the party to total. for (uint256 j; j < tokenIds.length; ++j) { // Must be retrieved before burning the token. uint256 shareOfVotingPower = getVotingPowerShareOf(tokenIds[j]); withdrawAmounts[i] += (balance * shareOfVotingPower) / 1e18; } } } { // Burn caller's party cards. This will revert if caller is not the // the owner or approved for any of the card they are attempting to // burn or if there are duplicate token IDs. uint96 totalVotingPowerBurned = _burnAndUpdateVotingPower(tokenIds, true); // Update total voting power of party. _governanceValues.totalVotingPower -= totalVotingPowerBurned; } { uint16 feeBps_ = feeBps; for (uint256 i; i < withdrawTokens.length; ++i) { IERC20 token = withdrawTokens[i]; uint256 amount = withdrawAmounts[i]; // Take fee from amount. uint256 fee = (amount * feeBps_) / 1e4; if (fee > 0) { amount -= fee; // Transfer fee to fee recipient. if (address(token) == ETH_ADDRESS) { payable(feeRecipient).transferEth(fee); } else { token.compatTransfer(feeRecipient, fee); } } if (amount > 0) { uint256 minAmount = minWithdrawAmounts[i]; // Check amount is at least minimum. if (amount < minAmount) { revert BelowMinWithdrawAmountError(amount, minAmount); } // Transfer token from party to recipient. if (address(token) == ETH_ADDRESS) { payable(receiver).transferEth(amount); } else { token.compatTransfer(receiver, amount); } } } } // Update ragequit timestamp back to before. rageQuitTimestamp = currentRageQuitTimestamp; emit RageQuit(msg.sender, tokenIds, withdrawTokens, receiver); } /// @inheritdoc ERC721 function transferFrom(address owner, address to, uint256 tokenId) public override { // Transfer voting along with token. _transferVotingPower(owner, to, votingPowerByTokenId[tokenId]); super.transferFrom(owner, to, tokenId); } /// @inheritdoc ERC721 function safeTransferFrom(address owner, address to, uint256 tokenId) public override { // super.safeTransferFrom() will call transferFrom() first which will // transfer voting power. super.safeTransferFrom(owner, to, tokenId); } /// @inheritdoc ERC721 function safeTransferFrom( address owner, address to, uint256 tokenId, bytes calldata data ) public override { // super.safeTransferFrom() will call transferFrom() first which will // transfer voting power. super.safeTransferFrom(owner, to, tokenId, data); } /// @notice Add a new authority. /// @dev Used in `AddAuthorityProposal`. Only the party itself can add /// authorities to prevent it from being used anywhere else. function addAuthority(address authority) external onlySelf { isAuthority[authority] = true; emit AuthorityAdded(authority); } /// @notice Relinquish the authority role. function abdicateAuthority() external onlyAuthority { delete isAuthority[msg.sender]; emit AuthorityRemoved(msg.sender); } function _delegateToRenderer() private view { _readOnlyDelegateCall( // Instance of IERC721Renderer. _GLOBALS.getAddress(LibGlobals.GLOBAL_GOVERNANCE_NFT_RENDER_IMPL), msg.data ); assert(false); // Will not be reached. } }
// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.20; import "../distribution/ITokenDistributor.sol"; import "../utils/ReadOnlyDelegateCall.sol"; import "../tokens/IERC721.sol"; import "../tokens/IERC20.sol"; import "../tokens/ERC721Receiver.sol"; import "../tokens/ERC1155Receiver.sol"; import "../utils/LibERC20Compat.sol"; import "../utils/LibRawResult.sol"; import "../utils/LibSafeCast.sol"; import "../utils/IERC4906.sol"; import "../globals/IGlobals.sol"; import "../globals/LibGlobals.sol"; import "../proposals/IProposalExecutionEngine.sol"; import "../proposals/LibProposal.sol"; import "../proposals/ProposalStorage.sol"; import "./Party.sol"; /// @notice Base contract for a Party encapsulating all governance functionality. abstract contract PartyGovernance is ERC721Receiver, ERC1155Receiver, ProposalStorage, Implementation, IERC4906, ReadOnlyDelegateCall { using LibERC20Compat for IERC20; using LibRawResult for bytes; using LibSafeCast for uint256; using LibSafeCast for int192; using LibSafeCast for uint96; // States a proposal can be in. enum ProposalStatus { // The proposal does not exist. Invalid, // The proposal has been proposed (via `propose()`), has not been vetoed // by a party host, and is within the voting window. Members can vote on // the proposal and party hosts can veto the proposal. Voting, // The proposal has either exceeded its voting window without reaching // `passThresholdBps` of votes or was vetoed by a party host. Defeated, // The proposal reached at least `passThresholdBps` of votes but is still // waiting for `executionDelay` to pass before it can be executed. Members // can continue to vote on the proposal and party hosts can veto at this time. Passed, // Same as `Passed` but now `executionDelay` has been satisfied. Any member // may execute the proposal via `execute()`, unless `maxExecutableTime` // has arrived. Ready, // The proposal has been executed at least once but has further steps to // complete so it needs to be executed again. No other proposals may be // executed while a proposal is in the `InProgress` state. No voting or // vetoing of the proposal is allowed, however it may be forcibly cancelled // via `cancel()` if the `cancelDelay` has passed since being first executed. InProgress, // The proposal was executed and completed all its steps. No voting or // vetoing can occur and it cannot be cancelled nor executed again. Complete, // The proposal was executed at least once but did not complete before // `cancelDelay` seconds passed since the first execute and was forcibly cancelled. Cancelled } struct GovernanceOpts { // Address of initial party hosts. address[] hosts; // How long people can vote on a proposal. uint40 voteDuration; // How long to wait after a proposal passes before it can be // executed. uint40 executionDelay; // Minimum ratio of accept votes to consider a proposal passed, // in bps, where 10,000 == 100%. uint16 passThresholdBps; // Total voting power of governance NFTs. uint96 totalVotingPower; // Fee bps for distributions. uint16 feeBps; // Fee recipeint for distributions. address payable feeRecipient; } // Subset of `GovernanceOpts` that are commonly read together for // efficiency. struct GovernanceValues { uint40 voteDuration; uint40 executionDelay; uint16 passThresholdBps; uint96 totalVotingPower; } // A snapshot of voting power for a member. struct VotingPowerSnapshot { // The timestamp when the snapshot was taken. uint40 timestamp; // Voting power that was delegated to this user by others. uint96 delegatedVotingPower; // The intrinsic (not delegated from someone else) voting power of this user. uint96 intrinsicVotingPower; // Whether the user was delegated to another at this snapshot. bool isDelegated; } // Proposal details chosen by proposer. struct Proposal { // Time beyond which the proposal can no longer be executed. // If the proposal has already been executed, and is still InProgress, // this value is ignored. uint40 maxExecutableTime; // The minimum seconds this proposal can remain in the InProgress status // before it can be cancelled. uint40 cancelDelay; // Encoded proposal data. The first 4 bytes are the proposal type, followed // by encoded proposal args specific to the proposal type. See // ProposalExecutionEngine for details. bytes proposalData; } // Accounting and state tracking values for a proposal. struct ProposalStateValues { // When the proposal was proposed. uint40 proposedTime; // When the proposal passed the vote. uint40 passedTime; // When the proposal was first executed. uint40 executedTime; // When the proposal completed. uint40 completedTime; // Number of accept votes. uint96 votes; // -1 == vetoed // Number of total voting power at time proposal created. uint96 totalVotingPower; } // Storage states for a proposal. struct ProposalState { // Accounting and state tracking values. ProposalStateValues values; // Hash of the proposal. bytes32 hash; // Whether a member has voted for (accepted) this proposal already. mapping(address => bool) hasVoted; } event Proposed(uint256 proposalId, address proposer, Proposal proposal); event ProposalAccepted(uint256 proposalId, address voter, uint256 weight); event EmergencyExecute(address target, bytes data, uint256 amountEth); event ProposalPassed(uint256 indexed proposalId); event ProposalVetoed(uint256 indexed proposalId, address host); event ProposalExecuted(uint256 indexed proposalId, address executor, bytes nextProgressData); event ProposalCancelled(uint256 indexed proposalId); event DistributionCreated( ITokenDistributor.TokenType tokenType, address token, uint256 tokenId ); event PartyDelegateUpdated(address indexed owner, address indexed delegate); event HostStatusTransferred(address oldHost, address newHost); event EmergencyExecuteDisabled(); event PartyVotingSnapshotCreated( address indexed voter, uint40 timestamp, uint96 delegatedVotingPower, uint96 intrinsicVotingPower, bool isDelegated ); error MismatchedPreciousListLengths(); error BadProposalStatusError(ProposalStatus status); error BadProposalHashError(bytes32 proposalHash, bytes32 actualHash); error ExecutionTimeExceededError(uint40 maxExecutableTime, uint40 timestamp); error OnlyPartyHostError(); error OnlyActiveMemberError(); error OnlyTokenDistributorOrSelfError(); error InvalidDelegateError(); error BadPreciousListError(); error OnlyPartyDaoError(address notDao, address partyDao); error OnlyPartyDaoOrHostError(address notDao, address partyDao); error OnlyWhenEmergencyActionsAllowedError(); error OnlyWhenEnabledError(); error AlreadyVotedError(address voter); error InvalidNewHostError(); error ProposalCannotBeCancelledYetError(uint40 currentTime, uint40 cancelTime); error InvalidBpsError(uint16 bps); error DistributionsRequireVoteError(); error PartyNotStartedError(); error CannotRageQuitAndAcceptError(); uint256 private constant UINT40_HIGH_BIT = 1 << 39; uint96 private constant VETO_VALUE = type(uint96).max; // The `Globals` contract storing global configuration values. This contract // is immutable and it’s address will never change. IGlobals private immutable _GLOBALS; /// @notice Whether the DAO has emergency powers for this party. bool public emergencyExecuteDisabled; /// @notice Distribution fee bps. uint16 public feeBps; /// @notice Distribution fee recipient. address payable public feeRecipient; /// @notice The timestamp of the last time `rageQuit()` was called. uint40 public lastRageQuitTimestamp; /// @notice The hash of the list of precious NFTs guarded by the party. bytes32 public preciousListHash; /// @notice The last proposal ID that was used. 0 means no proposals have been made. uint256 public lastProposalId; /// @notice Whether an address is a party host. mapping(address => bool) public isHost; /// @notice The last person a voter delegated its voting power to. mapping(address => address) public delegationsByVoter; // Governance parameters for this party. GovernanceValues internal _governanceValues; // ProposalState by proposal ID. mapping(uint256 => ProposalState) private _proposalStateByProposalId; // Snapshots of voting power per user, each sorted by increasing time. mapping(address => VotingPowerSnapshot[]) private _votingPowerSnapshotsByVoter; modifier onlyHost() { if (!isHost[msg.sender]) { revert OnlyPartyHostError(); } _; } // Caller must have voting power at the current time. modifier onlyActiveMember() { { VotingPowerSnapshot memory snap = _getLastVotingPowerSnapshotForVoter(msg.sender); // Must have either delegated voting power or intrinsic voting power. if (snap.intrinsicVotingPower == 0 && snap.delegatedVotingPower == 0) { revert OnlyActiveMemberError(); } } _; } // Only the party DAO multisig can call. modifier onlyPartyDao() { { address partyDao = _GLOBALS.getAddress(LibGlobals.GLOBAL_DAO_WALLET); if (msg.sender != partyDao) { revert OnlyPartyDaoError(msg.sender, partyDao); } } _; } // Only the party DAO multisig or a party host can call. modifier onlyPartyDaoOrHost() { address partyDao = _GLOBALS.getAddress(LibGlobals.GLOBAL_DAO_WALLET); if (msg.sender != partyDao && !isHost[msg.sender]) { revert OnlyPartyDaoOrHostError(msg.sender, partyDao); } _; } // Only if `emergencyExecuteDisabled` is not true. modifier onlyWhenEmergencyExecuteAllowed() { if (emergencyExecuteDisabled) { revert OnlyWhenEmergencyActionsAllowedError(); } _; } modifier onlyWhenNotGloballyDisabled() { if (_GLOBALS.getBool(LibGlobals.GLOBAL_DISABLE_PARTY_ACTIONS)) { revert OnlyWhenEnabledError(); } _; } // Set the `Globals` contract. constructor(IGlobals globals) { _GLOBALS = globals; } // Initialize storage for proxy contracts and initialize the proposal execution engine. function _initialize( GovernanceOpts memory govOpts, ProposalStorage.ProposalEngineOpts memory proposalEngineOpts, IERC721[] memory preciousTokens, uint256[] memory preciousTokenIds ) internal virtual { // Check BPS are valid. if (govOpts.feeBps > 1e4) { revert InvalidBpsError(govOpts.feeBps); } if (govOpts.passThresholdBps > 1e4) { revert InvalidBpsError(govOpts.passThresholdBps); } // Initialize the proposal execution engine. _initProposalImpl( IProposalExecutionEngine(_GLOBALS.getAddress(LibGlobals.GLOBAL_PROPOSAL_ENGINE_IMPL)), abi.encode(proposalEngineOpts) ); // Set the governance parameters. _governanceValues = GovernanceValues({ voteDuration: govOpts.voteDuration, executionDelay: govOpts.executionDelay, passThresholdBps: govOpts.passThresholdBps, totalVotingPower: govOpts.totalVotingPower }); // Set fees. feeBps = govOpts.feeBps; feeRecipient = govOpts.feeRecipient; // Set the precious list. _setPreciousList(preciousTokens, preciousTokenIds); // Set the party hosts. for (uint256 i = 0; i < govOpts.hosts.length; ++i) { isHost[govOpts.hosts[i]] = true; } } /// @dev Forward all unknown read-only calls to the proposal execution engine. /// Initial use case is to facilitate eip-1271 signatures. fallback() external { _readOnlyDelegateCall(address(_getSharedProposalStorage().engineImpl), msg.data); } /// @inheritdoc EIP165 /// @dev Combined logic for `ERC721Receiver` and `ERC1155Receiver`. function supportsInterface( bytes4 interfaceId ) public pure virtual override(ERC721Receiver, ERC1155Receiver) returns (bool) { return ERC721Receiver.supportsInterface(interfaceId) || ERC1155Receiver.supportsInterface(interfaceId) || // ERC4906 interface ID interfaceId == 0x49064906; } /// @notice Get the current `ProposalExecutionEngine` instance. function getProposalExecutionEngine() external view returns (IProposalExecutionEngine) { return _getSharedProposalStorage().engineImpl; } /// @notice Get the current `ProposalEngineOpts` options. function getProposalEngineOpts() external view returns (ProposalEngineOpts memory) { return _getSharedProposalStorage().opts; } /// @notice Get the total voting power of `voter` at a `timestamp`. /// @param voter The address of the voter. /// @param timestamp The timestamp to get the voting power at. /// @return votingPower The total voting power of `voter` at `timestamp`. function getVotingPowerAt( address voter, uint40 timestamp ) external view returns (uint96 votingPower) { return getVotingPowerAt(voter, timestamp, type(uint256).max); } /// @notice Get the total voting power of `voter` at a snapshot `snapIndex`, with checks to /// make sure it is the latest voting snapshot =< `timestamp`. /// @param voter The address of the voter. /// @param timestamp The timestamp to get the voting power at. /// @param snapIndex The index of the snapshot to get the voting power at. /// @return votingPower The total voting power of `voter` at `timestamp`. function getVotingPowerAt( address voter, uint40 timestamp, uint256 snapIndex ) public view returns (uint96 votingPower) { VotingPowerSnapshot memory snap = _getVotingPowerSnapshotAt(voter, timestamp, snapIndex); return (snap.isDelegated ? 0 : snap.intrinsicVotingPower) + snap.delegatedVotingPower; } /// @notice Get the state of a proposal. /// @param proposalId The ID of the proposal. /// @return status The status of the proposal. /// @return values The state of the proposal. function getProposalStateInfo( uint256 proposalId ) external view returns (ProposalStatus status, ProposalStateValues memory values) { values = _proposalStateByProposalId[proposalId].values; status = _getProposalStatus(values); } /// @notice Retrieve fixed governance parameters. /// @return gv The governance parameters of this party. function getGovernanceValues() external view returns (GovernanceValues memory gv) { return _governanceValues; } /// @notice Get the hash of a proposal. /// @dev Proposal details are not stored on-chain so the hash is used to enforce /// consistency between calls. /// @param proposal The proposal to hash. /// @return proposalHash The hash of the proposal. function getProposalHash(Proposal memory proposal) public pure returns (bytes32 proposalHash) { // Hash the proposal in-place. Equivalent to: // keccak256(abi.encode( // proposal.maxExecutableTime, // proposal.cancelDelay, // keccak256(proposal.proposalData) // )) bytes32 dataHash = keccak256(proposal.proposalData); assembly { // Overwrite the data field with the hash of its contents and then // hash the struct. let dataPos := add(proposal, 0x40) let t := mload(dataPos) mstore(dataPos, dataHash) proposalHash := keccak256(proposal, 0x60) // Restore the data field. mstore(dataPos, t) } } /// @notice Get the index of the most recent voting power snapshot <= `timestamp`. /// @param voter The address of the voter. /// @param timestamp The timestamp to get the snapshot index at. /// @return index The index of the snapshot. function findVotingPowerSnapshotIndex( address voter, uint40 timestamp ) public view returns (uint256 index) { VotingPowerSnapshot[] storage snaps = _votingPowerSnapshotsByVoter[voter]; // Derived from Open Zeppelin binary search // ref: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Checkpoints.sol#L39 uint256 high = snaps.length; uint256 low = 0; while (low < high) { uint256 mid = (low + high) / 2; if (snaps[mid].timestamp > timestamp) { // Entry is too recent. high = mid; } else { // Entry is older. This is our best guess for now. low = mid + 1; } } // Return `type(uint256).max` if no valid voting snapshots found. return high == 0 ? type(uint256).max : high - 1; } /// @notice Pledge your intrinsic voting power to a new delegate, removing it from /// the old one (if any). /// @param delegate The address to delegating voting power to. function delegateVotingPower(address delegate) external { _adjustVotingPower(msg.sender, 0, delegate); } /// @notice Transfer party host status to another. /// @param newPartyHost The address of the new host. function abdicateHost(address newPartyHost) external onlyHost { // 0 is a special case burn address. if (newPartyHost != address(0)) { // Cannot transfer host status to an existing host. if (isHost[newPartyHost]) { revert InvalidNewHostError(); } isHost[newPartyHost] = true; } isHost[msg.sender] = false; emit HostStatusTransferred(msg.sender, newPartyHost); } /// @notice Create a token distribution by moving the party's entire balance /// to the `TokenDistributor` contract and immediately creating a /// distribution governed by this party. /// @dev The `feeBps` and `feeRecipient` this party was created with will be /// propagated to the distribution. Party members are entitled to a /// share of the distribution's tokens proportionate to their relative /// voting power in this party (less the fee). /// @dev Allow this to be called by the party itself for `FractionalizeProposal`. /// @param tokenType The type of token to distribute. /// @param token The address of the token to distribute. /// @param tokenId The ID of the token to distribute. Currently unused but /// may be used in the future to support other distribution types. /// @return distInfo The information about the created distribution. function distribute( uint256 amount, ITokenDistributor.TokenType tokenType, address token, uint256 tokenId ) external onlyWhenNotGloballyDisabled returns (ITokenDistributor.DistributionInfo memory distInfo) { // Ignore if the party is calling functions on itself, like with // `FractionalizeProposal` and `DistributionProposal`. if (msg.sender != address(this)) { // Must not require a vote to create a distribution, otherwise // distributions can only be created through a distribution // proposal. if (_getSharedProposalStorage().opts.distributionsRequireVote) { revert DistributionsRequireVoteError(); } // Must be an active member. VotingPowerSnapshot memory snap = _getLastVotingPowerSnapshotForVoter(msg.sender); if (snap.intrinsicVotingPower == 0 && snap.delegatedVotingPower == 0) { revert OnlyActiveMemberError(); } } // Prevent creating a distribution if the party has not started. if (_governanceValues.totalVotingPower == 0) { revert PartyNotStartedError(); } // Get the address of the token distributor. ITokenDistributor distributor = ITokenDistributor( _GLOBALS.getAddress(LibGlobals.GLOBAL_TOKEN_DISTRIBUTOR) ); emit DistributionCreated(tokenType, token, tokenId); // Notify third-party platforms that the governance NFT metadata has // updated for all tokens. emit BatchMetadataUpdate(0, type(uint256).max); // Create a native token distribution. address payable feeRecipient_ = feeRecipient; uint16 feeBps_ = feeBps; if (tokenType == ITokenDistributor.TokenType.Native) { return distributor.createNativeDistribution{ value: amount }( Party(payable(address(this))), feeRecipient_, feeBps_ ); } // Otherwise must be an ERC20 token distribution. assert(tokenType == ITokenDistributor.TokenType.Erc20); IERC20(token).compatTransfer(address(distributor), amount); return distributor.createErc20Distribution( IERC20(token), Party(payable(address(this))), feeRecipient_, feeBps_ ); } /// @notice Make a proposal for members to vote on and cast a vote to accept it /// as well. /// @dev Only an active member (has voting power) can call this. /// Afterwards, members can vote to support it with `accept()` or a party /// host can unilaterally reject the proposal with `veto()`. /// @param proposal The details of the proposal. /// @param latestSnapIndex The index of the caller's most recent voting power /// snapshot before the proposal was created. Should /// be retrieved off-chain and passed in. function propose( Proposal memory proposal, uint256 latestSnapIndex ) external onlyActiveMember returns (uint256 proposalId) { proposalId = ++lastProposalId; // Store the time the proposal was created and the proposal hash. ( _proposalStateByProposalId[proposalId].values, _proposalStateByProposalId[proposalId].hash ) = ( ProposalStateValues({ proposedTime: uint40(block.timestamp), passedTime: 0, executedTime: 0, completedTime: 0, votes: 0, totalVotingPower: _governanceValues.totalVotingPower }), getProposalHash(proposal) ); emit Proposed(proposalId, msg.sender, proposal); accept(proposalId, latestSnapIndex); // Notify third-party platforms that the governance NFT metadata has // updated for all tokens. emit BatchMetadataUpdate(0, type(uint256).max); } /// @notice Vote to support a proposed proposal. /// @dev The voting power cast will be the effective voting power of the caller /// just before `propose()` was called (see `getVotingPowerAt()`). /// If the proposal reaches `passThresholdBps` acceptance ratio then the /// proposal will be in the `Passed` state and will be executable after /// the `executionDelay` has passed, putting it in the `Ready` state. /// @param proposalId The ID of the proposal to accept. /// @param snapIndex The index of the caller's last voting power snapshot /// before the proposal was created. Should be retrieved /// off-chain and passed in. /// @return totalVotes The total votes cast on the proposal. function accept(uint256 proposalId, uint256 snapIndex) public returns (uint256 totalVotes) { // Get the information about the proposal. ProposalState storage info = _proposalStateByProposalId[proposalId]; ProposalStateValues memory values = info.values; // Can only vote in certain proposal statuses. { ProposalStatus status = _getProposalStatus(values); // Allow voting even if the proposal is passed/ready so it can // potentially reach 100% consensus, which unlocks special // behaviors for certain proposal types. if ( status != ProposalStatus.Voting && status != ProposalStatus.Passed && status != ProposalStatus.Ready ) { revert BadProposalStatusError(status); } } // Prevent voting in the same block as the last rage quit timestamp. // This is to prevent an exploit where a member can rage quit to reduce // the total voting power of the party, then propose and vote in the // same block since `getVotingPowerAt()` uses `values.proposedTime - 1`. // This would allow them to use the voting power snapshot just before // their card was burned to vote, potentially passing a proposal that // would have otherwise not passed. if (lastRageQuitTimestamp == block.timestamp) { revert CannotRageQuitAndAcceptError(); } // Cannot vote twice. if (info.hasVoted[msg.sender]) { revert AlreadyVotedError(msg.sender); } // Mark the caller as having voted. info.hasVoted[msg.sender] = true; // Increase the total votes that have been cast on this proposal. uint96 votingPower = getVotingPowerAt(msg.sender, values.proposedTime - 1, snapIndex); values.votes += votingPower; info.values = values; emit ProposalAccepted(proposalId, msg.sender, votingPower); // Update the proposal status if it has reached the pass threshold. if ( values.passedTime == 0 && _areVotesPassing( values.votes, values.totalVotingPower, _governanceValues.passThresholdBps ) ) { info.values.passedTime = uint40(block.timestamp); emit ProposalPassed(proposalId); // Notify third-party platforms that the governance NFT metadata has // updated for all tokens. emit BatchMetadataUpdate(0, type(uint256).max); } return values.votes; } /// @notice As a party host, veto a proposal, unilaterally rejecting it. /// @dev The proposal will never be executable and cannot be voted on anymore. /// A proposal that has been already executed at least once (in the `InProgress` status) /// cannot be vetoed. /// @param proposalId The ID of the proposal to veto. function veto(uint256 proposalId) external onlyHost { // Setting `votes` to -1 indicates a veto. ProposalState storage info = _proposalStateByProposalId[proposalId]; ProposalStateValues memory values = info.values; { ProposalStatus status = _getProposalStatus(values); // Proposal must be in one of the following states. if ( status != ProposalStatus.Voting && status != ProposalStatus.Passed && status != ProposalStatus.Ready ) { revert BadProposalStatusError(status); } } // -1 indicates veto. info.values.votes = VETO_VALUE; emit ProposalVetoed(proposalId, msg.sender); // Notify third-party platforms that the governance NFT metadata has // updated for all tokens. emit BatchMetadataUpdate(0, type(uint256).max); } /// @notice Executes a proposal that has passed governance. /// @dev The proposal must be in the `Ready` or `InProgress` status. /// A `ProposalExecuted` event will be emitted with a non-empty `nextProgressData` /// if the proposal has extra steps (must be executed again) to carry out, /// in which case `nextProgressData` should be passed into the next `execute()` call. /// The `ProposalExecutionEngine` enforces that only one `InProgress` proposal /// is active at a time, so that proposal must be completed or cancelled via `cancel()` /// in order to execute a different proposal. /// `extraData` is optional, off-chain data a proposal might need to execute a step. /// @param proposalId The ID of the proposal to execute. /// @param proposal The details of the proposal. /// @param preciousTokens The tokens that the party considers precious. /// @param preciousTokenIds The token IDs associated with each precious token. /// @param progressData The data returned from the last `execute()` call, if any. /// @param extraData Off-chain data a proposal might need to execute a step. function execute( uint256 proposalId, Proposal memory proposal, IERC721[] memory preciousTokens, uint256[] memory preciousTokenIds, bytes calldata progressData, bytes calldata extraData ) external payable onlyActiveMember onlyWhenNotGloballyDisabled onlyDelegateCall { // Get information about the proposal. ProposalState storage proposalState = _proposalStateByProposalId[proposalId]; // Proposal details must remain the same from `propose()`. _validateProposalHash(proposal, proposalState.hash); ProposalStateValues memory values = proposalState.values; ProposalStatus status = _getProposalStatus(values); // The proposal must be executable or have already been executed but still // has more steps to go. if (status != ProposalStatus.Ready && status != ProposalStatus.InProgress) { revert BadProposalStatusError(status); } if (status == ProposalStatus.Ready) { // If the proposal has not been executed yet, make sure it hasn't // expired. Note that proposals that have been executed // (but still have more steps) ignore `maxExecutableTime`. if (proposal.maxExecutableTime < block.timestamp) { revert ExecutionTimeExceededError( proposal.maxExecutableTime, uint40(block.timestamp) ); } proposalState.values.executedTime = uint40(block.timestamp); } // Check that the precious list is valid. if (!_isPreciousListCorrect(preciousTokens, preciousTokenIds)) { revert BadPreciousListError(); } // Preemptively set the proposal to completed to avoid it being executed // again in a deeper call. proposalState.values.completedTime = uint40(block.timestamp); // Execute the proposal. bool completed = _executeProposal( proposalId, proposal, preciousTokens, preciousTokenIds, _getProposalFlags(values), progressData, extraData ); if (!completed) { // Proposal did not complete. proposalState.values.completedTime = 0; } } /// @notice Cancel a (probably stuck) InProgress proposal. /// @dev `proposal.cancelDelay` seconds must have passed since it was first /// executed for this to be valid. The currently active proposal will /// simply be yeeted out of existence so another proposal can execute. /// This is intended to be a last resort and can leave the party in a /// broken state. Whenever possible, active proposals should be /// allowed to complete their lifecycle. /// @param proposalId The ID of the proposal to cancel. /// @param proposal The details of the proposal to cancel. function cancel(uint256 proposalId, Proposal calldata proposal) external onlyActiveMember { // Get information about the proposal. ProposalState storage proposalState = _proposalStateByProposalId[proposalId]; // Proposal details must remain the same from `propose()`. _validateProposalHash(proposal, proposalState.hash); ProposalStateValues memory values = proposalState.values; { // Must be `InProgress`. ProposalStatus status = _getProposalStatus(values); if (status != ProposalStatus.InProgress) { revert BadProposalStatusError(status); } } { // Limit the `cancelDelay` to the global max and min cancel delay // to mitigate parties accidentally getting stuck forever by setting an // unrealistic `cancelDelay` or being reckless with too low a // cancel delay. uint256 cancelDelay = proposal.cancelDelay; uint256 globalMaxCancelDelay = _GLOBALS.getUint256( LibGlobals.GLOBAL_PROPOSAL_MAX_CANCEL_DURATION ); uint256 globalMinCancelDelay = _GLOBALS.getUint256( LibGlobals.GLOBAL_PROPOSAL_MIN_CANCEL_DURATION ); if (globalMaxCancelDelay != 0) { // Only if we have one set. if (cancelDelay > globalMaxCancelDelay) { cancelDelay = globalMaxCancelDelay; } } if (globalMinCancelDelay != 0) { // Only if we have one set. if (cancelDelay < globalMinCancelDelay) { cancelDelay = globalMinCancelDelay; } } uint256 cancelTime = values.executedTime + cancelDelay; // Must not be too early. if (block.timestamp < cancelTime) { revert ProposalCannotBeCancelledYetError( uint40(block.timestamp), uint40(cancelTime) ); } } // Mark the proposal as cancelled by setting the completed time to the current // time with the high bit set. proposalState.values.completedTime = uint40(block.timestamp | UINT40_HIGH_BIT); { // Delegatecall into the proposal engine impl to perform the cancel. (bool success, bytes memory resultData) = ( address(_getSharedProposalStorage().engineImpl) ).delegatecall(abi.encodeCall(IProposalExecutionEngine.cancelProposal, (proposalId))); if (!success) { resultData.rawRevert(); } } emit ProposalCancelled(proposalId); // Notify third-party platforms that the governance NFT metadata has // updated for all tokens. emit BatchMetadataUpdate(0, type(uint256).max); } /// @notice As the DAO, execute an arbitrary function call from this contract. /// @dev Emergency actions must not be revoked for this to work. /// @param targetAddress The contract to call. /// @param targetCallData The data to pass to the contract. /// @param amountEth The amount of ETH to send to the contract. function emergencyExecute( address targetAddress, bytes calldata targetCallData, uint256 amountEth ) external payable onlyPartyDao onlyWhenEmergencyExecuteAllowed onlyDelegateCall { (bool success, bytes memory res) = targetAddress.call{ value: amountEth }(targetCallData); if (!success) { res.rawRevert(); } emit EmergencyExecute(targetAddress, targetCallData, amountEth); } /// @notice Revoke the DAO's ability to call emergencyExecute(). /// @dev Either the DAO or the party host can call this. function disableEmergencyExecute() external onlyPartyDaoOrHost { emergencyExecuteDisabled = true; emit EmergencyExecuteDisabled(); } function _executeProposal( uint256 proposalId, Proposal memory proposal, IERC721[] memory preciousTokens, uint256[] memory preciousTokenIds, uint256 flags, bytes memory progressData, bytes memory extraData ) private returns (bool completed) { // Setup the arguments for the proposal execution engine. IProposalExecutionEngine.ExecuteProposalParams memory executeParams = IProposalExecutionEngine.ExecuteProposalParams({ proposalId: proposalId, proposalData: proposal.proposalData, progressData: progressData, extraData: extraData, preciousTokens: preciousTokens, preciousTokenIds: preciousTokenIds, flags: flags }); // Get the progress data returned after the proposal is executed. bytes memory nextProgressData; { // Execute the proposal. (bool success, bytes memory resultData) = address( _getSharedProposalStorage().engineImpl ).delegatecall( abi.encodeCall(IProposalExecutionEngine.executeProposal, (executeParams)) ); if (!success) { resultData.rawRevert(); } nextProgressData = abi.decode(resultData, (bytes)); } emit ProposalExecuted(proposalId, msg.sender, nextProgressData); // Notify third-party platforms that the governance NFT metadata has // updated for all tokens. emit BatchMetadataUpdate(0, type(uint256).max); // If the returned progress data is empty, then the proposal completed // and it should not be executed again. return nextProgressData.length == 0; } // Get the most recent voting power snapshot <= timestamp using `hintindex` as a "hint". function _getVotingPowerSnapshotAt( address voter, uint40 timestamp, uint256 hintIndex ) internal view returns (VotingPowerSnapshot memory snap) { VotingPowerSnapshot[] storage snaps = _votingPowerSnapshotsByVoter[voter]; uint256 snapsLength = snaps.length; if (snapsLength != 0) { if ( // Hint is within bounds. hintIndex < snapsLength && // Snapshot is not too recent. snaps[hintIndex].timestamp <= timestamp && // Snapshot is not too old. (hintIndex == snapsLength - 1 || snaps[hintIndex + 1].timestamp > timestamp) ) { return snaps[hintIndex]; } // Hint was wrong, fallback to binary search to find snapshot. hintIndex = findVotingPowerSnapshotIndex(voter, timestamp); // Check that snapshot was found. if (hintIndex != type(uint256).max) { return snaps[hintIndex]; } } // No snapshot found. return snap; } // Transfers some voting power of `from` to `to`. The total voting power of // their respective delegates will be updated as well. function _transferVotingPower(address from, address to, uint256 power) internal { int192 powerI192 = power.safeCastUint256ToInt192(); _adjustVotingPower(from, -powerI192, address(0)); _adjustVotingPower(to, powerI192, address(0)); } // Increase `voter`'s intrinsic voting power and update their delegate if delegate is nonzero. function _adjustVotingPower(address voter, int192 votingPower, address delegate) internal { VotingPowerSnapshot memory oldSnap = _getLastVotingPowerSnapshotForVoter(voter); address oldDelegate = delegationsByVoter[voter]; // If `oldDelegate` is zero and `voter` never delegated, then have // `voter` delegate to themself. oldDelegate = oldDelegate == address(0) ? voter : oldDelegate; // If the new `delegate` is zero, use the current (old) delegate. delegate = delegate == address(0) ? oldDelegate : delegate; VotingPowerSnapshot memory newSnap = VotingPowerSnapshot({ timestamp: uint40(block.timestamp), delegatedVotingPower: oldSnap.delegatedVotingPower, intrinsicVotingPower: (oldSnap.intrinsicVotingPower.safeCastUint96ToInt192() + votingPower).safeCastInt192ToUint96(), isDelegated: delegate != voter }); _insertVotingPowerSnapshot(voter, newSnap); delegationsByVoter[voter] = delegate; // This event is emitted even if the delegate did not change. emit PartyDelegateUpdated(msg.sender, delegate); // Handle rebalancing delegates. _rebalanceDelegates(voter, oldDelegate, delegate, oldSnap, newSnap); } // Update the delegated voting power of the old and new delegates delegated to // by `voter` based on the snapshot change. function _rebalanceDelegates( address voter, address oldDelegate, address newDelegate, VotingPowerSnapshot memory oldSnap, VotingPowerSnapshot memory newSnap ) private { if (newDelegate == address(0) || oldDelegate == address(0)) { revert InvalidDelegateError(); } if (oldDelegate != voter && oldDelegate != newDelegate) { // Remove past voting power from old delegate. VotingPowerSnapshot memory oldDelegateSnap = _getLastVotingPowerSnapshotForVoter( oldDelegate ); VotingPowerSnapshot memory updatedOldDelegateSnap = VotingPowerSnapshot({ timestamp: uint40(block.timestamp), delegatedVotingPower: oldDelegateSnap.delegatedVotingPower - oldSnap.intrinsicVotingPower, intrinsicVotingPower: oldDelegateSnap.intrinsicVotingPower, isDelegated: oldDelegateSnap.isDelegated }); _insertVotingPowerSnapshot(oldDelegate, updatedOldDelegateSnap); } if (newDelegate != voter) { // Not delegating to self. // Add new voting power to new delegate. VotingPowerSnapshot memory newDelegateSnap = _getLastVotingPowerSnapshotForVoter( newDelegate ); uint96 newDelegateDelegatedVotingPower = newDelegateSnap.delegatedVotingPower + newSnap.intrinsicVotingPower; if (newDelegate == oldDelegate) { // If the old and new delegate are the same, subtract the old // intrinsic voting power of the voter, or else we will double // count a portion of it. newDelegateDelegatedVotingPower -= oldSnap.intrinsicVotingPower; } VotingPowerSnapshot memory updatedNewDelegateSnap = VotingPowerSnapshot({ timestamp: uint40(block.timestamp), delegatedVotingPower: newDelegateDelegatedVotingPower, intrinsicVotingPower: newDelegateSnap.intrinsicVotingPower, isDelegated: newDelegateSnap.isDelegated }); _insertVotingPowerSnapshot(newDelegate, updatedNewDelegateSnap); } } // Append a new voting power snapshot, overwriting the last one if possible. function _insertVotingPowerSnapshot(address voter, VotingPowerSnapshot memory snap) private { VotingPowerSnapshot[] storage voterSnaps = _votingPowerSnapshotsByVoter[voter]; uint256 n = voterSnaps.length; // If same timestamp as last entry, overwrite the last snapshot, otherwise append. if (n != 0) { VotingPowerSnapshot memory lastSnap = voterSnaps[n - 1]; if (lastSnap.timestamp == snap.timestamp) { voterSnaps[n - 1] = snap; return; } } voterSnaps.push(snap); emit PartyVotingSnapshotCreated( voter, snap.timestamp, snap.delegatedVotingPower, snap.intrinsicVotingPower, snap.isDelegated ); } function _getLastVotingPowerSnapshotForVoter( address voter ) private view returns (VotingPowerSnapshot memory snap) { VotingPowerSnapshot[] storage voterSnaps = _votingPowerSnapshotsByVoter[voter]; uint256 n = voterSnaps.length; if (n != 0) { snap = voterSnaps[n - 1]; } } function _getProposalFlags(ProposalStateValues memory pv) private pure returns (uint256) { if (_isUnanimousVotes(pv.votes, pv.totalVotingPower)) { return LibProposal.PROPOSAL_FLAG_UNANIMOUS; } return 0; } function _getProposalStatus( ProposalStateValues memory pv ) private view returns (ProposalStatus status) { // Never proposed. if (pv.proposedTime == 0) { return ProposalStatus.Invalid; } // Executed at least once. if (pv.executedTime != 0) { if (pv.completedTime == 0) { return ProposalStatus.InProgress; } // completedTime high bit will be set if cancelled. if (pv.completedTime & UINT40_HIGH_BIT == UINT40_HIGH_BIT) { return ProposalStatus.Cancelled; } return ProposalStatus.Complete; } // Vetoed. if (pv.votes == type(uint96).max) { return ProposalStatus.Defeated; } uint40 t = uint40(block.timestamp); GovernanceValues memory gv = _governanceValues; if (pv.passedTime != 0) { // Ready. if (pv.passedTime + gv.executionDelay <= t) { return ProposalStatus.Ready; } // If unanimous, we skip the execution delay. if (_isUnanimousVotes(pv.votes, pv.totalVotingPower)) { return ProposalStatus.Ready; } // Passed. return ProposalStatus.Passed; } // Voting window expired. if (pv.proposedTime + gv.voteDuration <= t) { return ProposalStatus.Defeated; } return ProposalStatus.Voting; } function _isUnanimousVotes( uint96 totalVotes, uint96 totalVotingPower ) private pure returns (bool) { uint256 acceptanceRatio = (totalVotes * 1e4) / totalVotingPower; // If >= 99.99% acceptance, consider it unanimous. // The minting formula for voting power is a bit lossy, so we check // for slightly less than 100%. return acceptanceRatio >= 0.9999e4; } function _areVotesPassing( uint96 voteCount, uint96 totalVotingPower, uint16 passThresholdBps ) private pure returns (bool) { return (uint256(voteCount) * 1e4) / uint256(totalVotingPower) >= uint256(passThresholdBps); } function _setPreciousList( IERC721[] memory preciousTokens, uint256[] memory preciousTokenIds ) private { if (preciousTokens.length != preciousTokenIds.length) { revert MismatchedPreciousListLengths(); } preciousListHash = _hashPreciousList(preciousTokens, preciousTokenIds); } function _isPreciousListCorrect( IERC721[] memory preciousTokens, uint256[] memory preciousTokenIds ) private view returns (bool) { return preciousListHash == _hashPreciousList(preciousTokens, preciousTokenIds); } function _hashPreciousList( IERC721[] memory preciousTokens, uint256[] memory preciousTokenIds ) internal pure returns (bytes32 h) { assembly { mstore(0x00, keccak256(add(preciousTokens, 0x20), mul(mload(preciousTokens), 0x20))) mstore(0x20, keccak256(add(preciousTokenIds, 0x20), mul(mload(preciousTokenIds), 0x20))) h := keccak256(0x00, 0x40) } } // Assert that the hash of a proposal matches expectedHash. function _validateProposalHash(Proposal memory proposal, bytes32 expectedHash) private pure { bytes32 actualHash = getProposalHash(proposal); if (expectedHash != actualHash) { revert BadProposalHashError(actualHash, expectedHash); } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8; interface IMetadataProvider { /// @notice Whether or not the metadata provider supports registrars that can /// set metadata for other instances. /// @dev See `MetadataRegistry` for more information on the registrar role. function supportsRegistrars() external view returns (bool); /// @notice Get the metadata for a Party instance. /// @param instance The address of the instance. /// @param tokenId The ID of the token to get the metadata for. /// @return metadata The encoded metadata. function getMetadata( address instance, uint256 tokenId ) external view returns (bytes memory metadata); }
// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.20; import "../utils/LibRawResult.sol"; abstract contract Multicall { using LibRawResult for bytes; /// @notice Perform multiple delegatecalls on ourselves. function multicall(bytes[] calldata multicallData) external { for (uint256 i; i < multicallData.length; ++i) { (bool s, bytes memory r) = address(this).delegatecall(multicallData[i]); if (!s) { r.rawRevert(); } } } }
// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.20; // Base contract for all contracts intended to be delegatecalled into. abstract contract Implementation { error OnlyDelegateCallError(); error OnlyConstructorError(); address public immutable IMPL; constructor() { IMPL = address(this); } // Reverts if the current function context is not inside of a delegatecall. modifier onlyDelegateCall() virtual { if (address(this) == IMPL) { revert OnlyDelegateCallError(); } _; } // Reverts if the current function context is not inside of a constructor. modifier onlyConstructor() { if (address(this).code.length != 0) { revert OnlyConstructorError(); } _; } }
// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.20; library LibSafeCast { error Uint256ToUint96CastOutOfRange(uint256 v); error Uint256ToInt192CastOutOfRange(uint256 v); error Int192ToUint96CastOutOfRange(int192 i192); error Uint256ToInt128CastOutOfRangeError(uint256 u256); error Uint256ToUint128CastOutOfRangeError(uint256 u256); error Uint256ToUint40CastOutOfRangeError(uint256 u256); function safeCastUint256ToUint96(uint256 v) internal pure returns (uint96) { if (v > uint256(type(uint96).max)) { revert Uint256ToUint96CastOutOfRange(v); } return uint96(v); } function safeCastUint256ToUint128(uint256 v) internal pure returns (uint128) { if (v > uint256(type(uint128).max)) { revert Uint256ToUint128CastOutOfRangeError(v); } return uint128(v); } function safeCastUint256ToInt192(uint256 v) internal pure returns (int192) { if (v > uint256(uint192(type(int192).max))) { revert Uint256ToInt192CastOutOfRange(v); } return int192(uint192(v)); } function safeCastUint96ToInt192(uint96 v) internal pure returns (int192) { return int192(uint192(v)); } function safeCastInt192ToUint96(int192 i192) internal pure returns (uint96) { if (i192 < 0 || i192 > int192(uint192(type(uint96).max))) { revert Int192ToUint96CastOutOfRange(i192); } return uint96(uint192(i192)); } function safeCastUint256ToInt128(uint256 x) internal pure returns (int128) { if (x > uint256(uint128(type(int128).max))) { revert Uint256ToInt128CastOutOfRangeError(x); } return int128(uint128(x)); } function safeCastUint256ToUint40(uint256 x) internal pure returns (uint40) { if (x > uint256(type(uint40).max)) { revert Uint256ToUint40CastOutOfRangeError(x); } return uint40(x); } }
// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.20; library LibRawResult { // Revert with the data in `b`. function rawRevert(bytes memory b) internal pure { assembly { revert(add(b, 32), mload(b)) } } // Return with the data in `b`. function rawReturn(bytes memory b) internal pure { assembly { return(add(b, 32), mload(b)) } } }
// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.20; import { Strings } from "../utils/vendor/Strings.sol"; enum Color { DEFAULT, GREEN, CYAN, BLUE, PURPLE, PINK, ORANGE, RED } enum ColorType { PRIMARY, SECONDARY, LIGHT, DARK } library LibRenderer { using Strings for uint256; using Strings for string; function calcAnimationVariables( string memory partyName ) external pure returns (uint256 duration, uint256 steps, uint256 delay, uint256 translateX) { translateX = bytes(partyName).length * 30 + 300; duration = translateX / 56; // Make duration even so that the animation delay is always exactly // half of the duration. if (duration % 2 != 0) duration += 1; delay = duration / 2; steps = translateX / 6; } function formatAsDecimalString( uint256 n, uint256 decimals, uint256 maxChars ) external pure returns (string memory) { string memory str = n.toString(); uint256 oneUnit = 10 ** decimals; if (n < 10 ** (decimals - 2)) { return "<0.01"; } else if (n < oneUnit) { // Preserve leading zeros for decimals. // (e.g. if 0.01, `n` will "1" so we need to prepend a "0"). return string.concat("0.", prependNumWithZeros(str, decimals).substring(0, maxChars - 1)); } else if (n >= 1000 * oneUnit) { return str.substring(0, maxChars); } else { uint256 i = bytes((n / oneUnit).toString()).length; return string.concat(str.substring(0, i), ".", str.substring(i, maxChars)); } } function prependNumWithZeros( string memory numStr, uint256 expectedLength ) public pure returns (string memory) { uint256 length = bytes(numStr).length; if (length < expectedLength) { for (uint256 i; i < expectedLength - length; ++i) { numStr = string.concat("0", numStr); } } return numStr; } function generateColorHex( Color color, ColorType colorType ) external pure returns (string memory colorHex) { if (color == Color.DEFAULT) { if (colorType == ColorType.PRIMARY) { return "#A7B8CF"; } else if (colorType == ColorType.SECONDARY) { return "#DCE5F0"; } else if (colorType == ColorType.LIGHT) { return "#91A6C3"; } else if (colorType == ColorType.DARK) { return "#50586D"; } } else if (color == Color.GREEN) { if (colorType == ColorType.PRIMARY) { return "#10B173"; } else if (colorType == ColorType.SECONDARY) { return "#93DCB7"; } else if (colorType == ColorType.LIGHT) { return "#00A25A"; } else if (colorType == ColorType.DARK) { return "#005E3B"; } } else if (color == Color.CYAN) { if (colorType == ColorType.PRIMARY) { return "#00C1FA"; } else if (colorType == ColorType.SECONDARY) { return "#B1EFFD"; } else if (colorType == ColorType.LIGHT) { return "#00B4EA"; } else if (colorType == ColorType.DARK) { return "#005669"; } } else if (color == Color.BLUE) { if (colorType == ColorType.PRIMARY) { return "#2C78F3"; } else if (colorType == ColorType.SECONDARY) { return "#B3D4FF"; } else if (colorType == ColorType.LIGHT) { return "#0E70E0"; } else if (colorType == ColorType.DARK) { return "#00286A"; } } else if (color == Color.PURPLE) { if (colorType == ColorType.PRIMARY) { return "#9B45DF"; } else if (colorType == ColorType.SECONDARY) { return "#D2ACF2"; } else if (colorType == ColorType.LIGHT) { return "#832EC9"; } else if (colorType == ColorType.DARK) { return "#47196B"; } } else if (color == Color.PINK) { if (colorType == ColorType.PRIMARY) { return "#FF6BF3"; } else if (colorType == ColorType.SECONDARY) { return "#FFC8FB"; } else if (colorType == ColorType.LIGHT) { return "#E652E2"; } else if (colorType == ColorType.DARK) { return "#911A96"; } } else if (color == Color.ORANGE) { if (colorType == ColorType.PRIMARY) { return "#FF8946"; } else if (colorType == ColorType.SECONDARY) { return "#FFE38B"; } else if (colorType == ColorType.LIGHT) { return "#E47B2F"; } else if (colorType == ColorType.DARK) { return "#732700"; } } else if (color == Color.RED) { if (colorType == ColorType.PRIMARY) { return "#EC0000"; } else if (colorType == ColorType.SECONDARY) { return "#FFA6A6"; } else if (colorType == ColorType.LIGHT) { return "#D70000"; } else if (colorType == ColorType.DARK) { return "#6F0000"; } } } function getCollectionImageAndBanner( Color color, bool isDarkMode ) external pure returns (string memory image, string memory banner) { if (isDarkMode) { if (color == Color.GREEN) { image = "QmdcjXrxj7EimjuNTLQp1uKM2zYhuF1WVkVjF6TpfNNXrf"; banner = "QmR3vqAV17SJiwksiCHV1cLQuf9TKZuar8NQu8GmKkHRXM"; } else if (color == Color.CYAN) { image = "QmS678DTkTTzFQEDiqj3AsW6wt6bi4bNhWbKcBM29HBhhB"; banner = "QmYSbXyPh9Lx2wmv6Z7SK8iFD9kyADxtaohV1gv6ZVKFj4"; } else if (color == Color.BLUE) { image = "QmX2k8beAjyVhPk1ZrK6KrwbqLk3fRNPPmknRti7zEtGQa"; banner = "QmaXN8MbcrjkHPt97Z7xeuTCx6wPkJ9xZN7ExZZwMabspy"; } else if (color == Color.PURPLE) { image = "Qmf8SrxKH3QZQCEzcMbA3UoJGZ1j2coTLaQFptWhZZvqhg"; banner = "QmWhpo9kN2Nf8ioWb7BKwqsCQKt3M2TduHPNqMqXz9jUK4"; } else if (color == Color.PINK) { image = "QmV5eT9DWvU5BJa4LVSemkoDKyjJBC56adk2JLWMXYEQfn"; banner = "QmP2NTyvMQ5yN1RY4nH1HfoTX2r6Ug6WZfN9GFf9toak8M"; } else if (color == Color.ORANGE) { image = "QmPirB7VFaao2ZUxLtM5WTCwZhE7c9Uy2heyNZF5t9PgsS"; banner = "QmUMgkjhrxedcLUvWt8VKrZdvrCGmjxWDSDXfm2M96nKUx"; } else if (color == Color.RED) { image = "QmNRZ3syuEiiAkWYRFs9BpQ5M38wv8tEu17J2sYwmMdeta"; banner = "QmQV1EjzwQsXdgi6C6ubfopZrprk6Ab9LVnggZBgJDg5C2"; } else { image = "QmNwGtGyYwDfS6ghQbDw5a9buv7auFXz63W3rDhzmxjVhw"; banner = "QmYUujTBgH6RTswZSiSzuy2GUojE6H9edaXeoSwwvh2T7o"; } } else { if (color == Color.GREEN) { image = "QmR7t2g2hrkMYyzhUMEANzEGX74FQcbg3c7eTvCcQucMst"; banner = "Qmc3zRfT6nC2G1KgoWpCvLDVjNi7x7KFgwButecj3sq9qg"; } else if (color == Color.CYAN) { image = "QmeiBRb9muNXej3dn4usjjdtbUpgYASfA5jmWqJRshivGH"; banner = "QmcsvMB2xiBKKMyKsBkC51TjWnyk42nNJnsudoGFsvvXCt"; } else if (color == Color.BLUE) { image = "QmaErgGsanUTo73RMgvizMg3c7x1d1X4t76Tti22Pc1xan"; banner = "QmSjhHF994xBd7wavV4mhj7GpZmw5euQUBxpiPbbmxExqp"; } else if (color == Color.PURPLE) { image = "QmeVJTcUpKQFSz5aBsVpQk8quoXEEZBNPAAMo3wHvdRzHa"; banner = "QmYx6aHYGitr6p8dHUa7n4nyew3pWy1Mfdn8fpFLejmhHC"; } else if (color == Color.PINK) { image = "QmY4JJkBEeHVYHdfCCXPd7bWkAhNRxuKTWdf9MssGxSmCG"; banner = "QmQH5CFf3qXG2oymGzwTDgUmPFBsaTw2Qbfhjk58VHcABd"; } else if (color == Color.ORANGE) { image = "QmYhB3vjBLPwPTC5SBidNbZhB5oBMBMpKy4g6ejTxmGLkK"; banner = "QmRP8cVjJyPRV7wXs5ApwbCFyTBpHXmHsDexbcN4o7aomd"; } else if (color == Color.RED) { image = "QmfG8HPMEsKwKJ8xX3i2JhJtCskuiUjZLe8NhvXFdYyFR2"; banner = "QmazXbqfFtQexkwFDbYkyvLF4xRSDrmEVQS4PSAs2ZtxDn"; } else { image = "QmZKE4XkPvU7Z8CdgK2Cn7gLQ4t8CDkkfnR1j5bZ2AfRJu"; banner = "QmTKCqLUQJt3VxGuUqLMj1jcCRRsaZwY4k757Wb7YPzmH2"; } } image = string.concat("ipfs://", image); banner = string.concat("ipfs://", banner); } }
// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8; // Modified from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Strings.sol library Strings { bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef"; uint8 private constant _ADDRESS_LENGTH = 20; /** * @dev Converts a `uint256` to its ASCII `string` decimal representation. */ function toString(uint256 value) internal pure returns (string memory) { // Inspired by OraclizeAPI's implementation - MIT licence // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol if (value == 0) { return "0"; } uint256 temp = value; uint256 digits; while (temp != 0) { digits++; temp /= 10; } bytes memory buffer = new bytes(digits); while (value != 0) { digits -= 1; buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); value /= 10; } return string(buffer); } /** * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. */ function toHexString(uint256 value) internal pure returns (string memory) { if (value == 0) { return "0x00"; } uint256 temp = value; uint256 length = 0; while (temp != 0) { length++; temp >>= 8; } return toHexString(value, length); } /** * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. */ function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { bytes memory buffer = new bytes(2 * length + 2); buffer[0] = "0"; buffer[1] = "x"; for (uint256 i = 2 * length + 1; i > 1; --i) { buffer[i] = _HEX_SYMBOLS[value & 0xf]; value >>= 4; } require(value == 0, "Strings: hex length insufficient"); return string(buffer); } /** * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation. */ function toHexString(address addr) internal pure returns (string memory) { return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH); } function substring( string memory str, uint startIndex, uint endIndex ) internal pure returns (string memory) { bytes memory strBytes = bytes(str); bytes memory result = new bytes(endIndex - startIndex); for (uint i = startIndex; i < endIndex; i++) { result[i - startIndex] = strBytes[i]; } return string(result); } }
// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8; /// [MIT License] /// @title Base64 /// @notice Provides a function for encoding some bytes in base64 /// @author Brecht Devos <[email protected]> library Base64 { bytes internal constant TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; /// @notice Encodes some bytes to the base64 representation function encode(bytes memory data) internal pure returns (string memory) { uint256 len = data.length; if (len == 0) return ""; // multiply by 4/3 rounded up uint256 encodedLen = 4 * ((len + 2) / 3); // Add some extra buffer at the end bytes memory result = new bytes(encodedLen + 32); bytes memory table = TABLE; assembly { let tablePtr := add(table, 1) let resultPtr := add(result, 32) for { let i := 0 } lt(i, len) { } { i := add(i, 3) let input := and(mload(add(data, i)), 0xffffff) let out := mload(add(tablePtr, and(shr(18, input), 0x3F))) out := shl(8, out) out := add(out, and(mload(add(tablePtr, and(shr(12, input), 0x3F))), 0xFF)) out := shl(8, out) out := add(out, and(mload(add(tablePtr, and(shr(6, input), 0x3F))), 0xFF)) out := shl(8, out) out := add(out, and(mload(add(tablePtr, and(input, 0x3F))), 0xFF)) out := shl(224, out) mstore(resultPtr, out) resultPtr := add(resultPtr, 4) } switch mod(len, 3) case 1 { mstore(sub(resultPtr, 2), shl(240, 0x3d3d)) } case 2 { mstore(sub(resultPtr, 1), shl(248, 0x3d)) } mstore(result, encodedLen) } return string(result); } }
// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.20; import { Strings } from "../utils/vendor/Strings.sol"; import { Color } from "../utils/LibRenderer.sol"; import { Party } from "contracts/party/Party.sol"; import { IGlobals } from "../globals/IGlobals.sol"; import { IFont } from "./fonts/IFont.sol"; import { IERC721Renderer } from "./IERC721Renderer.sol"; import { RendererStorage } from "./RendererStorage.sol"; abstract contract RendererBase is IERC721Renderer { using Strings for uint256; using Strings for string; IGlobals immutable _GLOBALS; RendererStorage immutable _storage; IFont immutable _font; constructor(IGlobals globals, RendererStorage rendererStorage, IFont font) { _GLOBALS = globals; _storage = rendererStorage; _font = font; } function contractURI() external view virtual returns (string memory); function getCustomizationChoices() internal view returns (bool isDarkMode, Color color) { // Get the customization preset ID chosen by the crowdfund or party instance. uint256 presetId = _storage.getPresetFor(address(this)); if (presetId == 0) { // Preset ID 0 is reserved. It is used to indicate to party instances // to use the same customization preset as the crowdfund. (bool success, bytes memory result) = address(this).staticcall( // Call mintAuthority abi.encodeWithSignature("mintAuthority()") ); if (success && result.length == 32) { address crowdfund = abi.decode(result, (address)); // Should return the crowdfund used to create the party, if the // party was created conventionally. Use the customization // preset chosen during crowdfund initialization. presetId = _storage.getPresetFor(crowdfund); // If the preset ID is still 0 (this shouldn't happen), fallback // to the default customization options. if (presetId == 0) return (false, Color.DEFAULT); } else { // Fallback to the default customization options. May happen if // called from a non-party contract (e.g. a crowdfund contract, // although this shouldn't happen). return (false, Color.DEFAULT); } } // Get the customization data for the preset chosen. bytes memory customizationData = _storage.customizationPresets(presetId); if (customizationData.length == 0) { // If the customization preset doesn't exist, fallback to the // default customization options. return (false, Color.DEFAULT); } // Check version number. Fallback to default if using different version. if (abi.decode(customizationData, (uint8)) == 1) { (, isDarkMode, color) = abi.decode(customizationData, (uint8, bool, Color)); } else { // Fallback to the default customization options. return (false, Color.DEFAULT); } } }
// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.20; import "solmate/utils/SSTORE2.sol"; import "../utils/Multicall.sol"; contract RendererStorage is Multicall { error AlreadySetError(); error NotOwnerError(address caller, address owner); event OwnershipTransferred(address previousOwner, address newOwner); uint256 constant CROWDFUND_CARD_DATA = 0; uint256 constant PARTY_CARD_DATA = 1; /// @notice Address allowed to store new data. address public owner; /// @notice Customization presets by ID, used for rendering cards. Begins at /// 1, 0 is reserved to indicate in `getPresetFor()` that a /// party instance use the preset set by the crowdfund instance that /// created it. mapping(uint256 => bytes) public customizationPresets; /// @notice Customization preset used by a crowdfund or party instance. mapping(address => uint256) public getPresetFor; /// @notice Addresses where URI data chunks are stored. mapping(uint256 => address) public files; modifier onlyOwner() { address owner_ = owner; if (msg.sender != owner_) { revert NotOwnerError(msg.sender, owner_); } _; } constructor(address _owner) { // Set the address allowed to write new data. owner = _owner; // Write URI data used by V1 of the renderers: files[CROWDFUND_CARD_DATA] = SSTORE2.write( bytes( '<path class="o" d="M118.4 419.5h5.82v1.73h-4.02v1.87h3.74v1.73h-3.74v1.94h4.11v1.73h-5.91v-9Zm9.93 1.76h-2.6v-1.76h7.06v1.76h-2.61v7.24h-1.85v-7.24Zm6.06-1.76h1.84v3.55h3.93v-3.55H142v9h-1.84v-3.67h-3.93v3.67h-1.84v-9Z"/><path class="o" d="M145 413a4 4 0 0 1 4 4v14a4 4 0 0 1-4 4H35a4 4 0 0 1-4-4v-14a4 4 0 0 1 4-4h110m0-1H35a5 5 0 0 0-5 5v14a5 5 0 0 0 5 5h110a5 5 0 0 0 5-5v-14a5 5 0 0 0-5-5Z"/><path d="M239.24 399.83h3.04c1.7 0 2.82 1 2.82 2.55 0 2.1-1.27 3.32-3.57 3.32h-1.97l-.71 3.3h-1.56l1.96-9.17Zm2.34 4.38c1.23 0 1.88-.58 1.88-1.68 0-.73-.49-1.2-1.48-1.2h-1.51l-.6 2.88h1.7Zm3.57 1.86c0-2.27 1.44-3.83 3.57-3.83 1.82 0 3.06 1.25 3.06 3.09 0 2.28-1.43 3.83-3.57 3.83-1.82 0-3.06-1.25-3.06-3.09Zm3.13 1.74c1.19 0 1.93-1.02 1.93-2.52 0-1.06-.62-1.69-1.56-1.69-1.19 0-1.93 1.02-1.93 2.52 0 1.06.62 1.69 1.56 1.69Zm4.74-5.41h1.49l.28 4.73 2.25-4.73h1.64l.23 4.77 2.25-4.77h1.56l-3.3 6.61h-1.62l-.25-5.04-2.42 5.04h-1.63l-.48-6.61Zm9.54 3.66c0-2.27 1.45-3.81 3.6-3.81 2 0 3.05 1.58 2.33 3.92h-4.46c0 1.1.81 1.68 2.05 1.68.8 0 1.45-.2 2.1-.59l-.31 1.46a4.2 4.2 0 0 1-2.04.44c-2.06 0-3.26-1.19-3.26-3.11Zm4.7-1.07c.12-.86-.31-1.46-1.22-1.46s-1.57.61-1.82 1.46h3.05Zm3.46-2.59h1.55l-.28 1.28c.81-1.7 2.56-1.36 2.77-1.29l-.35 1.46c-.18-.06-2.3-.63-2.82 1.68l-.74 3.48h-1.55l1.42-6.61Zm3.91 3.66c0-2.27 1.45-3.81 3.6-3.81 2 0 3.05 1.58 2.33 3.92h-4.46c0 1.1.81 1.68 2.05 1.68.8 0 1.45-.2 2.1-.59l-.31 1.46a4.2 4.2 0 0 1-2.04.44c-2.06 0-3.26-1.19-3.26-3.11Zm4.7-1.07c.12-.86-.31-1.46-1.22-1.46s-1.57.61-1.82 1.46h3.05Zm2.25 1.36c0-2.44 1.36-4.1 3.26-4.1 1 0 1.76.53 2.05 1.31l.79-3.72h1.55l-1.96 9.17h-1.55l.2-.92a2.15 2.15 0 0 1-1.92 1.08c-1.49 0-2.43-1.18-2.43-2.82Zm3 1.51c.88 0 1.51-.58 1.73-1.56l.17-.81c.24-1.1-.31-1.93-1.36-1.93-1.19 0-1.94 1.08-1.94 2.59 0 1.06.55 1.71 1.4 1.71Zm9.6-.01-.25 1.16h-1.55l1.96-9.17h1.55l-.73 3.47a2.35 2.35 0 0 1 1.99-1.05c1.49 0 2.35 1.16 2.35 2.76 0 2.52-1.36 4.16-3.21 4.16-.98 0-1.81-.53-2.1-1.32Zm1.83.01c1.16 0 1.87-1.06 1.87-2.61 0-1.04-.5-1.69-1.39-1.69s-1.52.56-1.73 1.55l-.17.79c-.24 1.14.34 1.97 1.42 1.97Zm5.68 1.16-1.04-6.62h1.52l.66 4.75 2.66-4.75h1.69l-5.31 9.13h-1.73l1.55-2.51Zm23.48-6.8a42.14 42.14 0 0 0-.75 6.01 43.12 43.12 0 0 0 5.58 2.35 42.54 42.54 0 0 0 5.58-2.35 45.32 45.32 0 0 0-.75-6.01c-.91-.79-2.6-2.21-4.83-3.66a42.5 42.5 0 0 0-4.83 3.66Zm13.07-7.95s.82-.29 1.76-.45a14.9 14.9 0 0 0-9.53-3.81c.66.71 1.28 1.67 1.84 2.75 1.84.22 4.07.7 5.92 1.51Zm-2.71 18.36c-2.06-.4-4.05-.97-5.53-1.51a38.65 38.65 0 0 1-5.53 1.51c.12 1.5.35 3.04.76 4.58 0 0 1.54 1.82 4.78 2.8 3.23-.98 4.78-2.8 4.78-2.8.4-1.53.64-3.08.76-4.58Zm-13.77-18.37a22.3 22.3 0 0 1 5.93-1.51 12.4 12.4 0 0 1 1.84-2.75 14.97 14.97 0 0 0-9.53 3.81c.95.16 1.76.45 1.76.45Zm-4.72 8.77a25.74 25.74 0 0 0 3.58 2.94 37.48 37.48 0 0 1 4.08-4.04c.27-1.56.77-3.57 1.46-5.55a25.24 25.24 0 0 0-4.34-1.63s-2.35.42-4.81 2.74c-.77 3.29.04 5.54.04 5.54Zm25.92 0s.81-2.25.04-5.54c-2.46-2.31-4.81-2.74-4.81-2.74-1.53.42-2.99.99-4.34 1.63a37.79 37.79 0 0 1 1.46 5.55 37.44 37.44 0 0 1 4.08 4.04 25.86 25.86 0 0 0 3.58-2.94Zm-26.38.2s-.66-.56-1.27-1.3c-.7 3.34-.27 6.93 1.46 10.16.28-.93.8-1.94 1.46-2.97a22.32 22.32 0 0 1-1.66-5.88Zm8.24 14.27a22.07 22.07 0 0 1-4.27-4.38c-1.22.06-2.36 0-3.3-.22a14.91 14.91 0 0 0 8.07 6.34c-.34-.9-.5-1.75-.5-1.75Zm18.6-14.27s.66-.56 1.27-1.3c.7 3.34.27 6.93-1.46 10.16-.28-.93-.8-1.94-1.46-2.97a22.32 22.32 0 0 0 1.66-5.88Zm-8.24 14.27a22.07 22.07 0 0 0 4.27-4.38c1.22.06 2.36 0 3.3-.22a14.91 14.91 0 0 1-8.07 6.34c.34-.9.5-1.75.5-1.75ZM330 391.84l-4.12 2.45 1.26 3.91h5.72l1.26-3.91-4.12-2.45Zm-11.4 19.74 4.18 2.35 2.75-3.05-2.86-4.95-4.02.86-.06 4.79Zm22.79 0-.06-4.79-4.02-.86-2.86 4.95 2.75 3.05 4.18-2.35Z" style="fill:#00c1fa"/><use height="300" transform="matrix(1 0 0 .09 29.85 444)" width="300.15" xlink:href="#a"/><use height="21.15" transform="translate(30 446.92)" width="300" xlink:href="#b"/><g><path d="m191.54 428.67-28.09-24.34A29.98 29.98 0 0 0 143.8 397H30a15 15 0 0 0-15 15v98a15 15 0 0 0 15 15h300a15 15 0 0 0 15-15v-59a15 15 0 0 0-15-15H211.19a30 30 0 0 1-19.65-7.33Z" style="fill:url(#i)"/></g></svg>' ) ); files[PARTY_CARD_DATA] = SSTORE2.write( bytes( ' d="M188 444.3h2.4l2.6 8.2 2.7-8.2h2.3l-3.7 10.7h-2.8l-3.5-10.7zm10.5 5.3c0-3.2 2.2-5.6 5.3-5.6 3.1 0 5.3 2.3 5.3 5.6 0 3.2-2.2 5.5-5.3 5.5-3.1.1-5.3-2.2-5.3-5.5zm5.3 3.5c1.8 0 3-1.3 3-3.4 0-2.1-1.1-3.5-3-3.5s-3 1.3-3 3.5c0 2.1 1.1 3.4 3 3.4zm8.7-6.7h-3.1v-2.1h8.4v2.1h-3.1v8.6h-2.2v-8.6zm6.9-2.1h2.2V455h-2.2v-10.7zm4.3 0h2.9l4 8.2v-8.2h2.1V455h-2.9l-4-8.2v8.2h-2.1v-10.7zm10.6 5.4c0-3.4 2.3-5.6 6-5.6 1.2 0 2.3.2 3.1.6v2.3c-.9-.6-1.9-.8-3.1-.8-2.4 0-3.8 1.3-3.8 3.5 0 2.1 1.3 3.4 3.5 3.4.5 0 .9-.1 1.3-.2v-2.2h-2.2v-1.9h4.3v5.6c-1 .5-2.2.8-3.4.8-3.5 0-5.7-2.2-5.7-5.5zm15.1-5.4h4.3c2.3 0 3.7 1.3 3.7 3.5s-1.4 3.5-3.7 3.5h-2.1v3.7h-2.2v-10.7zm4.1 5c1.1 0 1.6-.5 1.6-1.5s-.5-1.5-1.6-1.5h-1.9v2.9h1.9zm4.8.3c0-3.2 2.2-5.6 5.3-5.6 3.1 0 5.3 2.3 5.3 5.6 0 3.2-2.2 5.5-5.3 5.5-3.1.1-5.3-2.2-5.3-5.5zm5.3 3.5c1.8 0 3-1.3 3-3.4 0-2.1-1.1-3.5-3-3.5s-3 1.3-3 3.5c0 2.1 1.1 3.4 3 3.4zm5.8-8.8h2.3l1.7 7.8 1.9-7.8h2.4l1.8 7.8 1.8-7.8h2.3l-2.7 10.7h-2.5l-1.9-8.2-1.8 8.2h-2.5l-2.8-10.7zm15.4 0h6.9v2.1H287v2.2h4.5v2.1H287v2.3h4.9v2.1h-7v-10.8zm9 0h4.5c2 0 3.3 1.3 3.3 3.2 0 1.9-1.2 3.1-3 3.2l3.5 4.3h-2.7l-3.5-4.4v4.4h-2.1v-10.7zm4.1 4.8c1 0 1.5-.5 1.5-1.4 0-.9-.6-1.4-1.5-1.4h-2v2.9h2zM30 444.3h4.3c3 0 5.2 2.1 5.2 5.4s-2.1 5.4-5.2 5.4H30v-10.8zm4 8.6c2.1 0 3.2-1.2 3.2-3.2s-1.2-3.3-3.2-3.3h-1.8v6.5H34zm7.7-8.6h2.2V455h-2.2v-10.7zm4.8 10V452c1 .7 2.1 1.1 3.2 1.1s1.7-.5 1.7-1.2-.4-1-1.2-1.2l-1.2-.3c-1.8-.5-2.7-1.5-2.7-3.1 0-2 1.5-3.2 3.9-3.2 1 0 2.1.2 2.9.7v2.3c-.9-.6-1.9-.8-3-.8-.9 0-1.6.4-1.6 1.1 0 .6.4.9 1.2 1.1l1.3.4c1.8.5 2.6 1.4 2.6 3.1 0 2.1-1.5 3.4-3.8 3.4-1.1-.2-2.3-.5-3.3-1.1zm12-7.9h-3.1v-2.1h8.4v2.1h-3.1v8.6h-2.2v-8.6zm7.5-2.1h4.5c2 0 3.3 1.3 3.3 3.2 0 1.9-1.2 3.1-3 3.2l3.5 4.3h-2.7l-3.5-4.4v4.4H66v-10.7zm4.1 4.8c1 0 1.5-.5 1.5-1.4s-.6-1.4-1.5-1.4h-2v2.9h2zm6.1-4.8h2.2V455h-2.2v-10.7zm5 0h4.5c2 0 3.2 1.1 3.2 2.8 0 1.1-.5 1.9-1.4 2.3 1.1.3 1.8 1.3 1.8 2.5 0 1.9-1.3 3.1-3.5 3.1h-4.6v-10.7zm4.2 4.4c.9 0 1.4-.5 1.4-1.3s-.5-1.3-1.4-1.3h-2.1v2.5l2.1.1zm.3 4.4c.9 0 1.5-.5 1.5-1.3s-.6-1.3-1.5-1.3h-2.4v2.6h2.4zm5.7-2.5v-6.3h2.2v6.3c0 1.6.9 2.5 2.3 2.5s2.3-.9 2.3-2.5v-6.3h2.2v6.3c0 2.9-1.7 4.6-4.5 4.6s-4.6-1.7-4.5-4.6zm14.2-4.2h-3.1v-2.1h8.4v2.1h-3.1v8.6h-2.2v-8.6zm7.5-2.1h2.2V455h-2.2v-10.7zm4.5 5.3c0-3.2 2.2-5.6 5.3-5.6s5.3 2.3 5.3 5.6-2.2 5.5-5.3 5.5-5.3-2.2-5.3-5.5zm5.3 3.5c1.8 0 3-1.3 3-3.5s-1.2-3.5-3-3.5-3 1.3-3 3.5 1.1 3.5 3 3.5zm7.5-8.8h2.9l4 8.2v-8.2h2.1V455h-2.9l-4-8.2v8.2h-2.1v-10.7zm11.7 10V452c1 .7 2.1 1.1 3.2 1.1s1.7-.5 1.7-1.2-.4-1-1.2-1.2l-1.2-.3c-1.8-.5-2.6-1.5-2.6-3.1 0-2 1.5-3.2 3.9-3.2 1.1 0 2.1.2 2.9.7v2.3c-.9-.6-1.9-.8-3-.8-.9 0-1.6.4-1.6 1.1 0 .6.4.9 1.2 1.1l1.3.4c1.8.5 2.6 1.4 2.6 3.1 0 2.1-1.5 3.4-3.8 3.4a9.7 9.7 0 0 1-3.4-1.1zM30 259.3h4.3c2.2 0 3.7 1.3 3.7 3.5s-1.4 3.5-3.7 3.5h-2.1v3.7H30v-10.7zm4.1 5c1.1 0 1.6-.5 1.6-1.5s-.5-1.5-1.6-1.5h-1.9v2.9h1.9zm6.1-5h4.5c2 0 3.3 1.3 3.3 3.2 0 1.9-1.2 3.1-3 3.2l3.5 4.3h-2.7l-3.5-4.4v4.4h-2.1v-10.7zm4.1 4.8c1 0 1.5-.5 1.5-1.4s-.6-1.4-1.5-1.4h-2v2.9h2zm5.4.5c0-3.2 2.2-5.6 5.3-5.6s5.3 2.3 5.3 5.6-2.2 5.5-5.3 5.5-5.3-2.2-5.3-5.5zm5.3 3.5c1.8 0 3-1.3 3-3.5s-1.2-3.5-3-3.5-3 1.3-3 3.5 1.1 3.5 3 3.5zm7.6-8.8h4.3c2.2 0 3.7 1.3 3.7 3.5s-1.4 3.5-3.7 3.5h-2.1v3.7h-2.2v-10.7zm4.1 5c1.1 0 1.6-.5 1.6-1.5s-.6-1.5-1.6-1.5h-1.9v2.9h1.9zm5.4.4c0-3.2 2.2-5.6 5.3-5.6s5.3 2.3 5.3 5.6-2.2 5.5-5.3 5.5-5.3-2.3-5.3-5.5zm5.4 3.4c1.8 0 3-1.3 3-3.5s-1.2-3.5-3-3.5-3 1.3-3 3.5 1.1 3.5 3 3.5zm7.2 1.2V267c1 .7 2.1 1.1 3.2 1.1s1.7-.5 1.7-1.2-.4-1-1.2-1.2l-1.2-.3c-1.8-.5-2.7-1.5-2.7-3.1 0-2 1.5-3.2 3.9-3.2 1.1 0 2.1.2 2.9.7v2.3c-.9-.6-1.9-.8-3-.8-.9 0-1.6.4-1.6 1.1 0 .6.4.9 1.2 1.1l1.3.4c1.8.5 2.6 1.4 2.6 3.1 0 2.1-1.5 3.4-3.8 3.4-1.1-.2-2.3-.5-3.3-1.1zm12.2-10h2.8l3.7 10.7h-2.3l-.8-2.5h-4l-.8 2.5h-2.2l3.6-10.7zm2.8 6.3-1.4-4.2-1.4 4.2h2.8zm5.7-6.3h2.2v8.6h4.7v2.1h-6.9v-10.7zm9.1 10V267c1 .7 2.1 1.1 3.2 1.1s1.7-.5 1.7-1.2-.4-1-1.2-1.2l-1.2-.3c-1.8-.5-2.7-1.5-2.7-3.1 0-2 1.5-3.2 3.9-3.2 1.1 0 2.1.2 2.9.7v2.3c-.9-.6-1.9-.8-3-.8-.9 0-1.6.4-1.6 1.1 0 .6.4.9 1.2 1.1l1.3.4c1.8.5 2.6 1.4 2.6 3.1 0 2.1-1.5 3.4-3.8 3.4-1.1-.2-2.3-.5-3.3-1.1zm-84.5-70h2.9l4 8.2v-8.2H39V210h-2.9l-4-8.2v8.2H30v-10.7zm14.7 0h2.8l3.7 10.7h-2.3l-.8-2.6h-4l-.8 2.6H41l3.7-10.7zm2.8 6.2-1.4-4.2-1.4 4.2h2.8zm5.7-6.2h3.3l2.5 8.2 2.5-8.2h3.3V210h-2v-8.6L60 210h-2.1l-2.7-8.5v8.5h-2v-10.7zm14.4 0h6.9v2.1h-4.8v2.2h4.4v2.1h-4.4v2.3h4.9v2.1h-7v-10.8z" /><path d="M239.24 24.83h3.04c1.7 0 2.82 1 2.82 2.55 0 2.1-1.27 3.32-3.57 3.32h-1.97l-.71 3.3h-1.56l1.96-9.17Zm2.34 4.38c1.23 0 1.88-.58 1.88-1.68 0-.73-.49-1.2-1.48-1.2h-1.51l-.6 2.88h1.7Zm3.57 1.86c0-2.27 1.44-3.83 3.57-3.83 1.82 0 3.06 1.25 3.06 3.09 0 2.28-1.43 3.83-3.57 3.83-1.82 0-3.06-1.25-3.06-3.09Zm3.13 1.74c1.19 0 1.93-1.02 1.93-2.52 0-1.06-.62-1.69-1.56-1.69-1.19 0-1.93 1.02-1.93 2.52 0 1.06.62 1.69 1.56 1.69Zm4.74-5.41h1.49l.28 4.73 2.25-4.73h1.64l.23 4.77 2.25-4.77h1.56l-3.3 6.61h-1.62l-.25-5.04-2.42 5.04h-1.63l-.48-6.61Zm9.54 3.66c0-2.27 1.45-3.81 3.6-3.81 2 0 3.05 1.58 2.33 3.92h-4.46c0 1.1.81 1.68 2.05 1.68.8 0 1.45-.2 2.1-.59l-.31 1.46a4.2 4.2 0 0 1-2.04.44c-2.06 0-3.26-1.19-3.26-3.11Zm4.7-1.07c.12-.86-.31-1.46-1.22-1.46s-1.57.61-1.82 1.46h3.05Zm3.46-2.59h1.55l-.28 1.28c.81-1.7 2.56-1.36 2.77-1.29l-.35 1.46c-.18-.06-2.3-.63-2.82 1.68l-.74 3.48h-1.55l1.42-6.61Zm3.91 3.66c0-2.27 1.45-3.81 3.6-3.81 2 0 3.05 1.58 2.33 3.92h-4.46c0 1.1.81 1.68 2.05 1.68.8 0 1.45-.2 2.1-.59l-.31 1.46a4.2 4.2 0 0 1-2.04.44c-2.06 0-3.26-1.19-3.26-3.11Zm4.7-1.07c.12-.86-.31-1.46-1.22-1.46s-1.57.61-1.82 1.46h3.05Zm2.25 1.36c0-2.44 1.36-4.1 3.26-4.1 1 0 1.76.53 2.05 1.31l.79-3.72h1.55l-1.96 9.17h-1.55l.2-.92a2.15 2.15 0 0 1-1.92 1.08c-1.49 0-2.43-1.18-2.43-2.82Zm3 1.51c.88 0 1.51-.58 1.73-1.56l.17-.81c.24-1.1-.31-1.93-1.36-1.93-1.19 0-1.94 1.08-1.94 2.59 0 1.06.55 1.71 1.4 1.71Zm9.6-.01-.25 1.16h-1.55l1.96-9.17h1.55l-.73 3.47a2.35 2.35 0 0 1 1.99-1.05c1.49 0 2.35 1.16 2.35 2.76 0 2.52-1.36 4.16-3.21 4.16-.98 0-1.81-.53-2.1-1.32Zm1.83.01c1.16 0 1.87-1.06 1.87-2.61 0-1.04-.5-1.69-1.39-1.69s-1.52.56-1.73 1.55l-.17.79c-.24 1.14.34 1.97 1.42 1.97Zm5.68 1.16-1.04-6.62h1.52l.66 4.75 2.66-4.75h1.69l-5.31 9.13h-1.73l1.55-2.51Zm23.47-6.8c.91-.79 2.6-2.21 4.83-3.66a42.5 42.5 0 0 1 4.83 3.66c.23 1.18.62 3.36.75 6.01a43.12 43.12 0 0 1-5.58 2.35 42.54 42.54 0 0 1-5.58-2.35c.14-2.65.53-4.83.75-6.01Zm13.07-7.95s.82-.29 1.76-.45a14.9 14.9 0 0 0-9.53-3.81c.66.71 1.28 1.67 1.84 2.75 1.84.22 4.07.7 5.92 1.51Zm-2.71 18.36c-2.06-.4-4.05-.97-5.53-1.51a38.65 38.65 0 0 1-5.53 1.51c.12 1.5.35 3.04.76 4.58 0 0 1.54 1.82 4.78 2.8 3.23-.98 4.78-2.8 4.78-2.8.4-1.53.64-3.08.76-4.58Zm-13.77-18.37a22.3 22.3 0 0 1 5.93-1.51 12.4 12.4 0 0 1 1.84-2.75 14.97 14.97 0 0 0-9.53 3.81c.95.16 1.76.45 1.76.45Zm-4.72 8.77a25.74 25.74 0 0 0 3.58 2.94 37.48 37.48 0 0 1 4.08-4.04c.27-1.56.77-3.57 1.46-5.55a25.24 25.24 0 0 0-4.34-1.63s-2.35.42-4.81 2.74c-.77 3.29.04 5.54.04 5.54Zm25.92 0s.81-2.25.04-5.54c-2.46-2.31-4.81-2.74-4.81-2.74-1.53.42-2.99.99-4.34 1.63a37.79 37.79 0 0 1 1.46 5.55 37.44 37.44 0 0 1 4.08 4.04 25.86 25.86 0 0 0 3.58-2.94Zm-26.38.2s-.66-.56-1.27-1.3c-.7 3.34-.27 6.93 1.46 10.16.28-.93.8-1.94 1.46-2.97a22.32 22.32 0 0 1-1.66-5.88Zm8.24 14.27a22.07 22.07 0 0 1-4.27-4.38c-1.22.06-2.36 0-3.3-.22a14.91 14.91 0 0 0 8.07 6.34c-.34-.9-.5-1.75-.5-1.75Zm18.6-14.27s.66-.56 1.27-1.3c.7 3.34.27 6.93-1.46 10.16-.28-.93-.8-1.94-1.46-2.97a22.32 22.32 0 0 0 1.66-5.88Zm-8.24 14.27a22.07 22.07 0 0 0 4.27-4.38c1.22.06 2.36 0 3.3-.22a14.91 14.91 0 0 1-8.07 6.34c.34-.9.5-1.75.5-1.75Zm-5.18-25.66-4.12 2.45 1.26 3.91h5.72l1.26-3.91-4.12-2.45Zm-11.4 19.74 4.18 2.35 2.75-3.05-2.86-4.95-4.02.86-.06 4.79Zm22.79 0-.06-4.79-4.02-.86-2.86 4.95 2.75 3.05 4.18-2.35Z" style="fill:#00c1fa"/><path d="M106.67 109.1a304.9 304.9 0 0 0-3.72-10.89c5.04-5.53 35.28-40.74 24.54-68.91 10.57 10.67 8.19 28.85 3.59 41.95-4.79 13.14-13.43 26.48-24.4 37.84Zm30.89 20.82c-5.87 6.12-20.46 17.92-21.67 18.77a99.37 99.37 0 0 0 7.94 6.02 133.26 133.26 0 0 0 20.09-18.48 353.47 353.47 0 0 0-6.36-6.31Zm-29.65-16.74a380.9 380.9 0 0 1 3.13 11.56c-4.8-1.37-8.66-2.53-12.36-3.82a123.4 123.4 0 0 1-21.16 13.21l15.84 5.47c14.83-8.23 28.13-20.82 37.81-34.68 0 0 8.56-12.55 12.42-23.68 2.62-7.48 4.46-16.57 3.49-24.89-2.21-12.27-6.95-15.84-9.32-17.66 6.16 5.72 3.25 27.8-2.79 39.89-6.08 12.16-15.73 24.27-27.05 34.59Zm59.05-37.86c-.03 7.72-3.05 15.69-6.44 22.69 1.7 2.2 3.18 4.36 4.42 6.49 7.97-16.51 3.74-26.67 2.02-29.18ZM61.18 128.51l12.5 4.3a101.45 101.45 0 0 0 21.42-13.19 163.26 163.26 0 0 1-10.61-4.51 101.28 101.28 0 0 1-23.3 13.4Zm87.78-42.73c.86.77 5.44 5.18 6.75 6.59 6.39-16.61.78-28.86-1.27-30.56.72 8.05-2.02 16.51-5.48 23.98Zm-14.29 40.62-2.47-15.18a142.42 142.42 0 0 1-35.74 29.45c6.81 2.36 12.69 4.4 15.45 5.38a115.98 115.98 0 0 0 22.75-19.66Zm-42.62 34.73c4.48 2.93 12.94 4.24 18.8 1.23 6.03-3.84-.6-8.34-8.01-9.88-9.8-2.03-16.82 1.22-13.4 6.21.41.6 1.19 1.5 2.62 2.44m-1.84.4c-3.56-2.37-6.77-7.2-.23-10.08 10.41-3.43 28.39 3.2 24.99 9.22-.58 1.04-1.46 1.6-2.38 2.19h-.03v.02h-.03v.02h-.03c-7.04 3.65-17.06 2.13-22.3-1.36m5.48-3.86a4.94 4.94 0 0 0 5.06.49l1.35-.74-4.68-2.38-1.47.79c-.38.22-1.53.88-.26 1.84m-1.7.59c-2.35-1.57-.78-2.61-.02-3.11 1.09-.57 2.19-1.15 3.28-1.77 6.95 3.67 7.22 3.81 13.19 6.17l-1.38.81c-1.93-.78-4.52-1.82-6.42-2.68.86 1.4 1.99 3.27 2.9 4.64l-1.68.87c-.75-1.28-1.76-2.99-2.47-4.29-3.19 2.06-6.99-.36-7.42-.64" style="fill:url(#f2)"/><path d="M159.13 52.37C143.51 24.04 119.45 15 103.6 15c-11.92 0-25.97 5.78-36.84 13.17 9.54 4.38 21.86 15.96 22.02 16.11-7.94-3.05-17.83-6.72-33.23-7.87a135.1 135.1 0 0 0-19.77 20.38c.77 7.66 2.88 15.68 2.88 15.68-6.28-4.75-11.02-4.61-18 9.45-5.4 12.66-6.93 24.25-4.65 33.18 0 0 4.72 26.8 36.23 40.07-1.3-4.61-1.58-9.91-.93-15.73a87.96 87.96 0 0 1-15.63-9.87c.79-6.61 2.79-13.82 6-21.36 4.42-10.66 4.35-15.14 4.35-15.19.03.07 5.48 12.43 12.95 22.08 4.23-8.84 9.46-16.08 13.67-21.83l-3.77-6.75a143.73 143.73 0 0 1 18.19-18.75c2.05 1.07 4.79 2.47 6.84 3.58 8.68-7.27 19.25-14.05 30.56-18.29-7-11.49-16.02-19.27-16.02-19.27s27.7 2.74 42.02 15.69a25.8 25.8 0 0 1 8.65 2.89ZM28.58 107.52a70.1 70.1 0 0 0-2.74 12.52 55.65 55.65 0 0 1-6.19-8.84 69.17 69.17 0 0 1 2.65-12.1c1.77-5.31 3.35-5.91 5.86-2.23v-.05c2.14 3.07 1.81 6.14.42 10.7ZM61.69 72.2l-.05.05a221.85 221.85 0 0 1-7.77-18.1l.14-.14a194.51 194.51 0 0 1 18.56 6.98 144.44 144.44 0 0 0-10.88 11.22Zm54.84-47.38c-4.42.7-9.02 1.95-13.67 3.72a65.03 65.03 0 0 0-7.81-5.31 66.04 66.04 0 0 1 13.02-3.54c1.53-.19 6.23-.79 10.32 2.42v-.05c2.47 1.91.14 2.37-1.86 2.75Z" style="fill:url(#h)"/>' ) ); } /// @notice Transfer ownership to a new owner. /// @param newOwner The address to transfer ownership to. function transferOwnership(address newOwner) external onlyOwner { emit OwnershipTransferred(owner, newOwner); owner = newOwner; } /// @notice Write data to be accessed by a given file key. /// @param key The key to access the written data. /// @param data The data to be written. function writeFile(uint256 key, string memory data) external onlyOwner { files[key] = SSTORE2.write(bytes(data)); } /// @notice Read data using a given file key. /// @param key The key to access the stored data. /// @return data The data stored at the given key. function readFile(uint256 key) external view returns (string memory data) { return string(SSTORE2.read(files[key])); } /// @notice Create or set a customization preset for renderers to use. /// @param id The ID of the customization preset. /// @param customizationData Data decoded by renderers used to render the SVG according to the preset. function createCustomizationPreset( uint256 id, bytes memory customizationData ) external onlyOwner { customizationPresets[id] = customizationData; } /// @notice For crowdfund or party instances to set the customization preset they want to use. /// @param id The ID of the customization preset. function useCustomizationPreset(uint256 id) external { getPresetFor[msg.sender] = id; } }
// SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.20; // Interface for `MetadataRegistry` contract from v1.1 of the protocol. interface IMetadataRegistry1_1 { function customPartyMetadataByCrowdfund( address crowdfundAddress ) external view returns ( string memory customName, string memory customDescription, string memory customImage ); }
// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.20; 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. contract TokenDistributor is ITokenDistributor { using LibAddress for address payable; using LibERC20Compat for IERC20; using LibRawResult for bytes; using LibSafeCast for uint256; struct DistributionState { // 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; } // Arguments for `_createDistribution()`. struct CreateDistributionArgs { Party party; TokenType tokenType; address token; uint256 currentTokenBalance; address payable feeRecipient; uint16 feeBps; } event EmergencyExecute(address target, bytes data); error OnlyPartyDaoError(address notDao, address partyDao); error InvalidDistributionInfoError(DistributionInfo info); error DistributionAlreadyClaimedByPartyTokenError(uint256 distributionId, uint256 partyTokenId); error DistributionFeeAlreadyClaimedError(uint256 distributionId); error MustOwnTokenError(address sender, address expectedOwner, uint256 partyTokenId); error EmergencyActionsNotAllowedError(); error InvalidDistributionSupplyError(uint128 supply); error OnlyFeeRecipientError(address caller, address feeRecipient); error InvalidFeeBpsError(uint16 feeBps); // Token address used to indicate a native distribution (i.e. distribution of ETH). address private constant NATIVE_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; /// @notice The `Globals` contract storing global configuration values. This contract /// is immutable and it’s address will never change. IGlobals public immutable GLOBALS; /// @notice Timestamp when the DAO is no longer allowed to call emergency functions. uint40 public immutable 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 => DistributionState mapping(Party => mapping(uint256 => DistributionState)) private _distributionStates; // msg.sender == DAO modifier onlyPartyDao() { { address partyDao = GLOBALS.getAddress(LibGlobals.GLOBAL_DAO_WALLET); if (msg.sender != partyDao) { revert OnlyPartyDaoError(msg.sender, partyDao); } } _; } // emergencyActionsDisabled == false modifier onlyIfEmergencyActionsAllowed() { if (block.timestamp > EMERGENCY_DISABLED_TIMESTAMP) { revert EmergencyActionsNotAllowedError(); } _; } // Set the `Globals` contract. constructor(IGlobals globals, uint40 emergencyDisabledTimestamp) { GLOBALS = globals; EMERGENCY_DISABLED_TIMESTAMP = emergencyDisabledTimestamp; } /// @inheritdoc ITokenDistributor function createNativeDistribution( Party party, address payable feeRecipient, uint16 feeBps ) external payable returns (DistributionInfo memory info) { info = _createDistribution( CreateDistributionArgs({ party: party, tokenType: TokenType.Native, token: NATIVE_TOKEN_ADDRESS, currentTokenBalance: address(this).balance, feeRecipient: feeRecipient, feeBps: feeBps }) ); } /// @inheritdoc ITokenDistributor function createErc20Distribution( IERC20 token, Party party, address payable feeRecipient, uint16 feeBps ) external returns (DistributionInfo memory info) { info = _createDistribution( CreateDistributionArgs({ party: party, tokenType: TokenType.Erc20, token: address(token), currentTokenBalance: token.balanceOf(address(this)), feeRecipient: feeRecipient, feeBps: feeBps }) ); } /// @inheritdoc ITokenDistributor function claim( DistributionInfo calldata info, uint256 partyTokenId ) public returns (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 ITokenDistributor function claimFee(DistributionInfo calldata info, address payable recipient) public { // 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 ITokenDistributor function batchClaim( DistributionInfo[] calldata infos, uint256[] calldata partyTokenIds ) external returns (uint128[] memory amountsClaimed) { amountsClaimed = new uint128[](infos.length); for (uint256 i = 0; i < infos.length; ++i) { amountsClaimed[i] = claim(infos[i], partyTokenIds[i]); } } /// @inheritdoc ITokenDistributor function batchClaimFee( DistributionInfo[] calldata infos, address payable[] calldata recipients ) external { for (uint256 i = 0; i < infos.length; ++i) { claimFee(infos[i], recipients[i]); } } /// @inheritdoc ITokenDistributor function getClaimAmount( DistributionInfo calldata info, uint256 partyTokenId ) public view returns (uint128) { Party party = info.party; // Check which method to use for calculating claim amount based on // version of Party contract. (bool success, bytes memory 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 ITokenDistributor function wasFeeClaimed(Party party, uint256 distributionId) external view returns (bool) { return _distributionStates[party][distributionId].wasFeeClaimed; } /// @inheritdoc ITokenDistributor function hasPartyTokenIdClaimed( Party party, uint256 partyTokenId, uint256 distributionId ) external view returns (bool) { return _distributionStates[party][distributionId].hasPartyTokenClaimed[partyTokenId]; } /// @inheritdoc ITokenDistributor function getRemainingMemberSupply( Party party, uint256 distributionId ) external view returns (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. function emergencyExecute( address targetAddress, bytes calldata targetCallData ) external onlyPartyDao onlyIfEmergencyActionsAllowed { (bool success, bytes memory res) = targetAddress.delegatecall(targetCallData); if (!success) { res.rawRevert(); } emit EmergencyExecute(targetAddress, targetCallData); } function _createDistribution( CreateDistributionArgs memory args ) private returns (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 }); ( _distributionStates[args.party][info.distributionId].distributionHash, _distributionStates[args.party][info.distributionId].remainingMemberSupply ) = (_getDistributionHash(info), memberSupply); emit DistributionCreated(args.party, info); } function _transfer( TokenType tokenType, address token, address payable recipient, uint256 amount ) private { bytes32 balanceId = _getBalanceId(tokenType, token); // Reduce stored token balance. uint256 storedBalance = _storedBalances[balanceId] - amount; // Temporarily set to max as a reentrancy guard. An interesing attack // could occur if we didn't do this where an attacker could `claim()` and // reenter upon transfer (e.g. in the `tokensToSend` hook of an ERC777) to // `createERC20Distribution()`. Since the `balanceOf(address(this))` // would not of been updated yet, the supply would be miscalculated and // the attacker would create a distribution that essentially steals from // the last distribution they were claiming from. Here, we prevent that // by causing an arithmetic underflow with the supply calculation if // this were to be attempted. _storedBalances[balanceId] = type(uint256).max; if (tokenType == TokenType.Native) { recipient.transferEth(amount); } else { assert(tokenType == TokenType.Erc20); IERC20(token).compatTransfer(recipient, amount); } _storedBalances[balanceId] = storedBalance; } function _getDistributionHash( DistributionInfo memory info ) internal pure returns (bytes32 hash) { assembly { hash := keccak256(info, 0x100) } } function _getBalanceId( TokenType tokenType, address token ) private pure returns (bytes32 balanceId) { if (tokenType == TokenType.Native) { return bytes32(uint256(uint160(NATIVE_TOKEN_ADDRESS))); } assert(tokenType == TokenType.Erc20); return bytes32(uint256(uint160(token))); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /// @title The interface on-chain font contracts must implement to be added to the registry. /// /// Uploading fonts to chain is open ended and up to the dev (SSTORE2 or hardcoded string or etc). /// /// As long as the font contract implements this interface and has immutable font data, it can be added /// to the registry. /// /// @author @0x_beans interface IFont { /// @notice Address that uploaded font for credits function fontUploader() external returns (address); /// @notice Format type of font (e.g. ttf, woff, otf, etc). Must be lowercase. /// This info is necessary so projects know how to properly render the fonts. function fontFormatType() external returns (string memory); /// @notice Font name (ie. 'space-grotesk'). Must be lowercase. function fontName() external returns (string memory); /// @notice Weight used by the font (e.g. bold, medium, light, etc). Must be lowercase. /// Necessary to differentiate uploaded fonts that are the same but different weights. function fontWeight() external returns (string memory); /// @notice Style used by the font (e.g. lowercase normal, italic, oblique, etc). Must be lowercase. // Necessary to differentiate uploaded fonts that are the same but different style. function fontStyle() external returns (string memory); /// @notice The full base64 encoded font with data URI scheme prefix /// (e.g. 'data:font/ttf;charset=utf-8;base64,'). function getFont() external view returns (string memory); }
// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.20; library LibAddress { error EthTransferFailed(address receiver, bytes errData); // Transfer ETH with full gas stipend. function transferEth(address payable receiver, uint256 amount) internal { if (amount == 0) return; (bool s, bytes memory r) = receiver.call{ value: amount }(""); if (!s) { revert EthTransferFailed(receiver, r); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC2981.sol) pragma solidity ^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._ */ interface IERC2981 is IERC165 { /** * @dev Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of * exchange. The royalty amount is denominated and should be paid in that same unit of exchange. */ function royaltyInfo( uint256 tokenId, uint256 salePrice ) external view returns (address receiver, uint256 royaltyAmount); }
// SPDX-License-Identifier: AGPL-3.0-only // Based on solmate commit 1681dc505f4897ef636f0435d01b1aa027fdafaf (v6.4.0) // @ https://github.com/Rari-Capital/solmate/blob/1681dc505f4897ef636f0435d01b1aa027fdafaf/src/tokens/ERC1155.sol // Only modified to inherit IERC721 and EIP165. pragma solidity >=0.8.0; // NOTE: Only modified to inherit IERC20 and EIP165 import "../../tokens/IERC721.sol"; import "../../utils/EIP165.sol"; /// @notice Modern, minimalist, and gas efficient ERC-721 implementation. /// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC721.sol) abstract contract ERC721 is IERC721, EIP165 { /*////////////////////////////////////////////////////////////// METADATA STORAGE/LOGIC //////////////////////////////////////////////////////////////*/ string public name; string public symbol; function tokenURI(uint256 id /* view */) public virtual returns (string memory); /*////////////////////////////////////////////////////////////// ERC721 BALANCE/OWNER STORAGE //////////////////////////////////////////////////////////////*/ mapping(uint256 => address) internal _ownerOf; mapping(address => uint256) internal _balanceOf; function ownerOf(uint256 id) public view virtual returns (address owner) { require((owner = _ownerOf[id]) != address(0), "NOT_MINTED"); } function balanceOf(address owner) public view virtual returns (uint256) { require(owner != address(0), "ZERO_ADDRESS"); return _balanceOf[owner]; } /*////////////////////////////////////////////////////////////// ERC721 APPROVAL STORAGE //////////////////////////////////////////////////////////////*/ mapping(uint256 => address) public getApproved; mapping(address => mapping(address => bool)) public isApprovedForAll; /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ constructor(string memory _name, string memory _symbol) { name = _name; symbol = _symbol; } /*////////////////////////////////////////////////////////////// ERC721 LOGIC //////////////////////////////////////////////////////////////*/ function approve(address spender, uint256 id) public virtual { address owner = _ownerOf[id]; require(msg.sender == owner || isApprovedForAll[owner][msg.sender], "NOT_AUTHORIZED"); getApproved[id] = spender; emit Approval(owner, spender, id); } function setApprovalForAll(address operator, bool approved) public virtual { isApprovedForAll[msg.sender][operator] = approved; emit ApprovalForAll(msg.sender, operator, approved); } function transferFrom(address from, address to, uint256 id) public virtual { require(from == _ownerOf[id], "WRONG_FROM"); require(to != address(0), "INVALID_RECIPIENT"); require( msg.sender == from || isApprovedForAll[from][msg.sender] || msg.sender == getApproved[id], "NOT_AUTHORIZED" ); // Underflow of the sender's balance is impossible because we check for // ownership above and the recipient's balance can't realistically overflow. unchecked { _balanceOf[from]--; _balanceOf[to]++; } _ownerOf[id] = to; delete getApproved[id]; emit Transfer(from, to, id); } function safeTransferFrom(address from, address to, uint256 id) public virtual { transferFrom(from, to, id); require( to.code.length == 0 || ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, "") == ERC721TokenReceiver.onERC721Received.selector, "UNSAFE_RECIPIENT" ); } function safeTransferFrom( address from, address to, uint256 id, bytes calldata data ) public virtual { transferFrom(from, to, id); require( to.code.length == 0 || ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, data) == ERC721TokenReceiver.onERC721Received.selector, "UNSAFE_RECIPIENT" ); } /*////////////////////////////////////////////////////////////// ERC165 LOGIC //////////////////////////////////////////////////////////////*/ function supportsInterface(bytes4 interfaceId) public pure virtual override returns (bool) { // NOTE: modified from original to call super. return super.supportsInterface(interfaceId) || interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721 interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata } /*////////////////////////////////////////////////////////////// INTERNAL MINT/BURN LOGIC //////////////////////////////////////////////////////////////*/ function _mint(address to, uint256 id) internal virtual { require(to != address(0), "INVALID_RECIPIENT"); require(_ownerOf[id] == address(0), "ALREADY_MINTED"); // Counter overflow is incredibly unrealistic. unchecked { _balanceOf[to]++; } _ownerOf[id] = to; emit Transfer(address(0), to, id); } function _burn(uint256 id) internal virtual { address owner = _ownerOf[id]; require(owner != address(0), "NOT_MINTED"); // Ownership check above ensures no underflow. unchecked { _balanceOf[owner]--; } delete _ownerOf[id]; delete getApproved[id]; emit Transfer(owner, address(0), id); } /*////////////////////////////////////////////////////////////// INTERNAL SAFE MINT LOGIC //////////////////////////////////////////////////////////////*/ function _safeMint(address to, uint256 id) internal virtual { _mint(to, id); require( to.code.length == 0 || ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, "") == ERC721TokenReceiver.onERC721Received.selector, "UNSAFE_RECIPIENT" ); } function _safeMint(address to, uint256 id, bytes memory data) internal virtual { _mint(to, id); require( to.code.length == 0 || ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, data) == ERC721TokenReceiver.onERC721Received.selector, "UNSAFE_RECIPIENT" ); } } /// @notice A generic interface for a contract which properly accepts ERC721 tokens. /// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC721.sol) abstract contract ERC721TokenReceiver { function onERC721Received( address, address, uint256, bytes calldata ) external virtual returns (bytes4) { return ERC721TokenReceiver.onERC721Received.selector; } }
// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.20; import "../tokens/IERC20.sol"; import "../party/Party.sol"; /// @notice Creates token distributions for parties. interface ITokenDistributor { enum TokenType { Native, Erc20 } // Info on a distribution, created by createDistribution(). struct DistributionInfo { // Type of distribution/token. TokenType tokenType; // ID of the distribution. Assigned by createDistribution(). uint256 distributionId; // The party whose members can claim the distribution. Party party; // Who can claim `fee`. address payable feeRecipient; // The token being distributed. address token; // Total amount of `token` that can be claimed by party members. uint128 memberSupply; // Amount of `token` to be redeemed by `feeRecipient`. uint128 fee; // Total shares at time distribution was created. uint96 totalShares; } event DistributionCreated(Party indexed party, DistributionInfo info); event DistributionFeeClaimed( Party indexed party, address indexed feeRecipient, TokenType tokenType, address token, uint256 amount ); event DistributionClaimedByPartyToken( Party indexed party, uint256 indexed partyTokenId, address indexed owner, TokenType tokenType, address token, uint256 amountClaimed ); /// @notice Create a new distribution for an outstanding native token balance /// governed by a party. /// @dev Native tokens should be transferred directly into this contract /// immediately prior (same tx) to calling `createDistribution()` or /// attached to the call itself. /// @param party The party whose members can claim the distribution. /// @param feeRecipient Who can claim `fee`. /// @param feeBps Percentage (in bps) of the distribution `feeRecipient` receives. /// @return info Information on the created distribution. function createNativeDistribution( Party party, address payable feeRecipient, uint16 feeBps ) external payable returns (DistributionInfo memory info); /// @notice Create a new distribution for an outstanding ERC20 token balance /// governed by a party. /// @dev ERC20 tokens should be transferred directly into this contract /// immediately prior (same tx) to calling `createDistribution()` or /// attached to the call itself. /// @param token The ERC20 token to distribute. /// @param party The party whose members can claim the distribution. /// @param feeRecipient Who can claim `fee`. /// @param feeBps Percentage (in bps) of the distribution `feeRecipient` receives. /// @return info Information on the created distribution. function createErc20Distribution( IERC20 token, Party party, address payable feeRecipient, uint16 feeBps ) external returns (DistributionInfo memory info); /// @notice Claim a portion of a distribution owed to a `partyTokenId` belonging /// to the party that created the distribution. The caller /// must own this token. /// @param info Information on the distribution being claimed. /// @param partyTokenId The ID of the party token to claim for. /// @return amountClaimed The amount of the distribution claimed. function claim( DistributionInfo calldata info, uint256 partyTokenId ) external returns (uint128 amountClaimed); /// @notice Claim the fee for a distribution. Only a distribution's `feeRecipient` /// can call this. /// @param info Information on the distribution being claimed. /// @param recipient The address to send the fee to. function claimFee(DistributionInfo calldata info, address payable recipient) external; /// @notice Batch version of `claim()`. /// @param infos Information on the distributions being claimed. /// @param partyTokenIds The ID of the party tokens to claim for. /// @return amountsClaimed The amount of the distributions claimed. function batchClaim( DistributionInfo[] calldata infos, uint256[] calldata partyTokenIds ) external returns (uint128[] memory amountsClaimed); /// @notice Batch version of `claimFee()`. /// @param infos Information on the distributions to claim fees for. /// @param recipients The addresses to send the fees to. function batchClaimFee( DistributionInfo[] calldata infos, address payable[] calldata recipients ) external; /// @notice Compute the amount of a distribution's token are owed to a party /// member, identified by the `partyTokenId`. /// @param info Information on the distribution being claimed. /// @param partyTokenId The ID of the party token to claim for. /// @return claimAmount The amount of the distribution owed to the party member. function getClaimAmount( DistributionInfo calldata info, uint256 partyTokenId ) external view returns (uint128); /// @notice Check whether the fee has been claimed for a distribution. /// @param party The party to use for checking whether the fee has been claimed. /// @param distributionId The ID of the distribution to check. /// @return feeClaimed Whether the fee has been claimed. function wasFeeClaimed(Party party, uint256 distributionId) external view returns (bool); /// @notice Check whether a `partyTokenId` has claimed their share of a distribution. /// @param party The party to use for checking whether the `partyTokenId` has claimed. /// @param partyTokenId The ID of the party token to check. /// @param distributionId The ID of the distribution to check. /// @return hasClaimed Whether the `partyTokenId` has claimed. function hasPartyTokenIdClaimed( Party party, uint256 partyTokenId, uint256 distributionId ) external view returns (bool); /// @notice Get how much unclaimed member tokens are left in a distribution. /// @param party The party to use for checking the unclaimed member tokens. /// @param distributionId The ID of the distribution to check. /// @return remainingMemberSupply The amount of distribution supply remaining. function getRemainingMemberSupply( Party party, uint256 distributionId ) external view returns (uint128); }
// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.20; import "./LibRawResult.sol"; interface IReadOnlyDelegateCall { // Marked `view` so that `_readOnlyDelegateCall` can be `view` as well. function delegateCallAndRevert(address impl, bytes memory callData) external view; } // Inherited by contracts to perform read-only delegate calls. abstract contract ReadOnlyDelegateCall { using LibRawResult for bytes; // Delegatecall into implement and revert with the raw result. function delegateCallAndRevert(address impl, bytes memory callData) external { // Attempt to gate to only `_readOnlyDelegateCall()` invocations. require(msg.sender == address(this)); (bool s, bytes memory r) = impl.delegatecall(callData); // Revert with success status and return data. abi.encode(s, r).rawRevert(); } // Perform a `delegateCallAndRevert()` then return the raw result data. function _readOnlyDelegateCall(address impl, bytes memory callData) internal view { try IReadOnlyDelegateCall(address(this)).delegateCallAndRevert(impl, callData) { // Should never happen. assert(false); } catch (bytes memory r) { (bool success, bytes memory resultData) = abi.decode(r, (bool, bytes)); if (!success) { resultData.rawRevert(); } resultData.rawReturn(); } } }
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8; // Minimal ERC20 interface. interface IERC20 { event Transfer(address indexed owner, address indexed to, uint256 amount); event Approval(address indexed owner, address indexed spender, uint256 allowance); function transfer(address to, uint256 amount) external returns (bool); function transferFrom(address from, address to, uint256 amount) external returns (bool); function approve(address spender, uint256 allowance) external returns (bool); function allowance(address owner, address spender) external view returns (uint256); function balanceOf(address owner) external view returns (uint256); }
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8; import "./IERC721Receiver.sol"; import "../utils/EIP165.sol"; import "../vendor/solmate/ERC721.sol"; /// @notice Mixin for contracts that want to receive ERC721 tokens. /// @dev Use this instead of solmate's ERC721TokenReceiver because the /// compiler has issues when overriding EIP165/IERC721Receiver functions. abstract contract ERC721Receiver is IERC721Receiver, EIP165, ERC721TokenReceiver { /// @inheritdoc IERC721Receiver function onERC721Received( address, address, uint256, bytes memory ) public virtual override(IERC721Receiver, ERC721TokenReceiver) returns (bytes4) { return IERC721Receiver.onERC721Received.selector; } /// @inheritdoc EIP165 function supportsInterface(bytes4 interfaceId) public pure virtual override returns (bool) { return EIP165.supportsInterface(interfaceId) || interfaceId == type(IERC721Receiver).interfaceId; } }
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8; import "../vendor/solmate/ERC1155.sol"; import "../utils/EIP165.sol"; abstract contract ERC1155Receiver is EIP165, ERC1155TokenReceiverBase { /// @inheritdoc EIP165 function supportsInterface(bytes4 interfaceId) public pure virtual override returns (bool) { return super.supportsInterface(interfaceId) || interfaceId == type(ERC1155TokenReceiverBase).interfaceId; } }
// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.20; import "../tokens/IERC20.sol"; // Compatibility helpers for ERC20s. library LibERC20Compat { error NotATokenError(IERC20 token); error TokenTransferFailedError(IERC20 token, address to, uint256 amount); error TokenApprovalFailed(IERC20 token, address spender, uint256 amount); // Perform an `IERC20.transfer()` handling non-compliant implementations. function compatTransfer(IERC20 token, address to, uint256 amount) internal { (bool s, bytes memory r) = address(token).call( abi.encodeCall(IERC20.transfer, (to, amount)) ); if (s) { if (r.length == 0) { uint256 cs; assembly { cs := extcodesize(token) } if (cs == 0) { revert NotATokenError(token); } return; } if (abi.decode(r, (bool))) { return; } } revert TokenTransferFailedError(token, to, amount); } // Perform an `IERC20.transferFrom()` handling non-compliant implementations. function compatTransferFrom(IERC20 token, address from, address to, uint256 amount) internal { (bool s, bytes memory 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); } function compatApprove(IERC20 token, address spender, uint256 amount) internal { (bool s, bytes memory 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); } }
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8; interface IERC4906 { event MetadataUpdate(uint256 _tokenId); event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId); }
// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.20; import "../tokens/IERC721.sol"; // Upgradeable proposals logic contract interface. interface IProposalExecutionEngine { struct ExecuteProposalParams { uint256 proposalId; bytes proposalData; bytes progressData; bytes extraData; uint256 flags; IERC721[] preciousTokens; uint256[] preciousTokenIds; } function initialize(address oldImpl, bytes memory initData) external; /// @notice Execute a proposal. /// @dev Must be delegatecalled into by PartyGovernance. /// If the proposal is incomplete, continues its next step (if possible). /// If another proposal is incomplete, this will fail. Only one /// incomplete proposal is allowed at a time. /// @param params The data needed to execute the proposal. /// @return nextProgressData Bytes to be passed into the next `execute()` call, /// if the proposal execution is incomplete. Otherwise, empty bytes /// to indicate the proposal is complete. function executeProposal( ExecuteProposalParams memory params ) external returns (bytes memory nextProgressData); /// @notice Forcibly cancel an incomplete proposal. /// @param proposalId The ID of the proposal to cancel. /// @dev This is intended to be a last resort as it can leave a party in a /// broken step. Whenever possible, proposals should be allowed to /// complete their entire lifecycle. function cancelProposal(uint256 proposalId) external; }
// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.20; import "../tokens/IERC721.sol"; library LibProposal { uint256 internal constant PROPOSAL_FLAG_UNANIMOUS = 0x1; function isTokenPrecious( IERC721 token, IERC721[] memory preciousTokens ) internal pure returns (bool) { for (uint256 i; i < preciousTokens.length; ++i) { if (token == preciousTokens[i]) { return true; } } return false; } function isTokenIdPrecious( IERC721 token, uint256 tokenId, IERC721[] memory preciousTokens, uint256[] memory preciousTokenIds ) internal pure returns (bool) { for (uint256 i; i < preciousTokens.length; ++i) { if (token == preciousTokens[i] && tokenId == preciousTokenIds[i]) { return true; } } return false; } }
// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.20; import "./IProposalExecutionEngine.sol"; import "../utils/LibRawResult.sol"; // The storage bucket shared by `PartyGovernance` and the `ProposalExecutionEngine`. // Read this for more context on the pattern motivating this: // https://github.com/dragonfly-xyz/useful-solidity-patterns/tree/main/patterns/explicit-storage-buckets abstract contract ProposalStorage { using LibRawResult for bytes; struct SharedProposalStorage { IProposalExecutionEngine engineImpl; ProposalEngineOpts opts; } struct ProposalEngineOpts { // Whether the party can add new authorities with the add authority proposal. bool enableAddAuthorityProposal; // Whether the party can spend ETH from the party's balance with // arbitrary call proposals. bool allowArbCallsToSpendPartyEth; // Whether operators can be used. bool allowOperators; // Whether distributions require a vote or can be executed by any active member. bool distributionsRequireVote; } uint256 internal constant PROPOSAL_FLAG_UNANIMOUS = 0x1; uint256 private constant SHARED_STORAGE_SLOT = uint256(keccak256("ProposalStorage.SharedProposalStorage")); function _initProposalImpl(IProposalExecutionEngine impl, bytes memory initData) internal { SharedProposalStorage storage stor = _getSharedProposalStorage(); IProposalExecutionEngine oldImpl = stor.engineImpl; stor.engineImpl = impl; (bool s, bytes memory r) = address(impl).delegatecall( abi.encodeCall(IProposalExecutionEngine.initialize, (address(oldImpl), initData)) ); if (!s) { r.rawRevert(); } } function _getSharedProposalStorage() internal pure returns (SharedProposalStorage storage stor) { uint256 s = SHARED_STORAGE_SLOT; assembly { stor.slot := s } } }
// SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.20; interface IERC721Renderer { function tokenURI(uint256 tokenId) external view returns (string memory); function contractURI() external view returns (string memory); }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; /// @notice Read and write to persistent storage at a fraction of the cost. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SSTORE2.sol) /// @author Modified from 0xSequence (https://github.com/0xSequence/sstore2/blob/master/contracts/SSTORE2.sol) library SSTORE2 { uint256 internal constant DATA_OFFSET = 1; // We skip the first byte as it's a STOP opcode to ensure the contract can't be called. /*////////////////////////////////////////////////////////////// WRITE LOGIC //////////////////////////////////////////////////////////////*/ function write(bytes memory data) internal returns (address pointer) { // Prefix the bytecode with a STOP opcode to ensure it cannot be called. bytes memory runtimeCode = abi.encodePacked(hex"00", data); bytes memory creationCode = abi.encodePacked( //---------------------------------------------------------------------------------------------------------------// // Opcode | Opcode + Arguments | Description | Stack View // //---------------------------------------------------------------------------------------------------------------// // 0x60 | 0x600B | PUSH1 11 | codeOffset // // 0x59 | 0x59 | MSIZE | 0 codeOffset // // 0x81 | 0x81 | DUP2 | codeOffset 0 codeOffset // // 0x38 | 0x38 | CODESIZE | codeSize codeOffset 0 codeOffset // // 0x03 | 0x03 | SUB | (codeSize - codeOffset) 0 codeOffset // // 0x80 | 0x80 | DUP | (codeSize - codeOffset) (codeSize - codeOffset) 0 codeOffset // // 0x92 | 0x92 | SWAP3 | codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset) // // 0x59 | 0x59 | MSIZE | 0 codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset) // // 0x39 | 0x39 | CODECOPY | 0 (codeSize - codeOffset) // // 0xf3 | 0xf3 | RETURN | // //---------------------------------------------------------------------------------------------------------------// hex"60_0B_59_81_38_03_80_92_59_39_F3", // Returns all code in the contract except for the first 11 (0B in hex) bytes. runtimeCode // The bytecode we want the contract to have after deployment. Capped at 1 byte less than the code size limit. ); /// @solidity memory-safe-assembly assembly { // Deploy a new contract with the generated creation code. // We start 32 bytes into the code to avoid copying the byte length. pointer := create(0, add(creationCode, 32), mload(creationCode)) } require(pointer != address(0), "DEPLOYMENT_FAILED"); } /*////////////////////////////////////////////////////////////// READ LOGIC //////////////////////////////////////////////////////////////*/ function read(address pointer) internal view returns (bytes memory) { return readBytecode(pointer, DATA_OFFSET, pointer.code.length - DATA_OFFSET); } function read(address pointer, uint256 start) internal view returns (bytes memory) { start += DATA_OFFSET; return readBytecode(pointer, start, pointer.code.length - start); } function read(address pointer, uint256 start, uint256 end) internal view returns (bytes memory) { start += DATA_OFFSET; end += DATA_OFFSET; require(pointer.code.length >= end, "OUT_OF_BOUNDS"); return readBytecode(pointer, start, end - start); } /*////////////////////////////////////////////////////////////// INTERNAL HELPER LOGIC //////////////////////////////////////////////////////////////*/ function readBytecode(address pointer, uint256 start, uint256 size) private view returns (bytes memory data) { /// @solidity memory-safe-assembly assembly { // Get a pointer to some free memory. data := mload(0x40) // Update the free memory pointer to prevent overriding our data. // We use and(x, not(31)) as a cheaper equivalent to sub(x, mod(x, 32)). // Adding 31 to size and running the result through the logic above ensures // the memory pointer remains word-aligned, following the Solidity convention. mstore(0x40, add(data, and(add(add(size, 32), 31), not(31)))) // Store the size of the data in the first 32 byte chunk of free memory. mstore(data, size) // Copy the code into memory right after the 32 bytes we used to store the size. extcodecopy(pointer, add(data, 32), start, size) } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) pragma solidity ^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}. */ interface IERC165 { /** * @dev Returns true if this contract implements the interface defined by * `interfaceId`. See the corresponding * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] * to learn more about how these ids are created. * * This function call must use less than 30 000 gas. */ function supportsInterface(bytes4 interfaceId) external view returns (bool); }
// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.20; abstract contract EIP165 { /// @notice Query if a contract implements an interface. /// @param interfaceId The interface identifier, as specified in ERC-165 /// @return `true` if the contract implements `interfaceId` and /// `interfaceId` is not 0xffffffff, `false` otherwise function supportsInterface(bytes4 interfaceId) public pure virtual returns (bool) { return interfaceId == this.supportsInterface.selector; } }
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8; interface IERC721Receiver { function onERC721Received( address operator, address from, uint256 tokenId, bytes memory data ) external returns (bytes4); }
// SPDX-License-Identifier: AGPL-3.0-only // Based on solmate commit 1681dc505f4897ef636f0435d01b1aa027fdafaf (v6.4.0) // @ https://github.com/Rari-Capital/solmate/blob/1681dc505f4897ef636f0435d01b1aa027fdafaf/src/tokens/ERC1155.sol // Only modified to inherit IERC1155 and rename ERC1155TokenReceiver -> ERC1155TokenReceiverBase. pragma solidity ^0.8; import "../../tokens/IERC1155.sol"; /// @notice Minimalist and gas efficient standard ERC1155 implementation. /// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC1155.sol) abstract contract ERC1155 is IERC1155 { /*////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ event URI(string value, uint256 indexed id); /*////////////////////////////////////////////////////////////// ERC1155 STORAGE //////////////////////////////////////////////////////////////*/ mapping(address => mapping(uint256 => uint256)) public balanceOf; mapping(address => mapping(address => bool)) public isApprovedForAll; /*////////////////////////////////////////////////////////////// METADATA LOGIC //////////////////////////////////////////////////////////////*/ function uri(uint256 id) public view virtual returns (string memory); /*////////////////////////////////////////////////////////////// ERC1155 LOGIC //////////////////////////////////////////////////////////////*/ function setApprovalForAll(address operator, bool approved) public virtual { isApprovedForAll[msg.sender][operator] = approved; emit ApprovalForAll(msg.sender, operator, approved); } function safeTransferFrom( address from, address to, uint256 id, uint256 amount, bytes calldata data ) public virtual { require(msg.sender == from || isApprovedForAll[from][msg.sender], "NOT_AUTHORIZED"); balanceOf[from][id] -= amount; balanceOf[to][id] += amount; emit TransferSingle(msg.sender, from, to, id, amount); require( to.code.length == 0 ? to != address(0) : ERC1155TokenReceiverBase(to).onERC1155Received( msg.sender, from, id, amount, data ) == ERC1155TokenReceiverBase.onERC1155Received.selector, "UNSAFE_RECIPIENT" ); } function safeBatchTransferFrom( address from, address to, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata data ) public virtual { require(ids.length == amounts.length, "LENGTH_MISMATCH"); require(msg.sender == from || isApprovedForAll[from][msg.sender], "NOT_AUTHORIZED"); // Storing these outside the loop saves ~15 gas per iteration. uint256 id; uint256 amount; for (uint256 i; i < ids.length; ) { id = ids[i]; amount = amounts[i]; balanceOf[from][id] -= amount; balanceOf[to][id] += amount; // An array can't have a total length // larger than the max uint256 value. unchecked { ++i; } } emit TransferBatch(msg.sender, from, to, ids, amounts); require( to.code.length == 0 ? to != address(0) : ERC1155TokenReceiverBase(to).onERC1155BatchReceived( msg.sender, from, ids, amounts, data ) == ERC1155TokenReceiverBase.onERC1155BatchReceived.selector, "UNSAFE_RECIPIENT" ); } function balanceOfBatch( address[] calldata owners, uint256[] calldata ids ) public view virtual returns (uint256[] memory balances) { require(owners.length == ids.length, "LENGTH_MISMATCH"); balances = new uint256[](owners.length); // Unchecked because the only math done is incrementing // the array index counter which cannot possibly overflow. unchecked { for (uint256 i; i < owners.length; ++i) { balances[i] = balanceOf[owners[i]][ids[i]]; } } } /*////////////////////////////////////////////////////////////// ERC165 LOGIC //////////////////////////////////////////////////////////////*/ function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { return interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165 interfaceId == 0xd9b67a26 || // ERC165 Interface ID for ERC1155 interfaceId == 0x0e89341c; // ERC165 Interface ID for ERC1155MetadataURI } /*////////////////////////////////////////////////////////////// INTERNAL MINT/BURN LOGIC //////////////////////////////////////////////////////////////*/ function _mint(address to, uint256 id, uint256 amount, bytes memory data) internal virtual { balanceOf[to][id] += amount; emit TransferSingle(msg.sender, address(0), to, id, amount); require( to.code.length == 0 ? to != address(0) : ERC1155TokenReceiverBase(to).onERC1155Received( msg.sender, address(0), id, amount, data ) == ERC1155TokenReceiverBase.onERC1155Received.selector, "UNSAFE_RECIPIENT" ); } function _batchMint( address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data ) internal virtual { uint256 idsLength = ids.length; // Saves MLOADs. require(idsLength == amounts.length, "LENGTH_MISMATCH"); for (uint256 i; i < idsLength; ) { balanceOf[to][ids[i]] += amounts[i]; // An array can't have a total length // larger than the max uint256 value. unchecked { ++i; } } emit TransferBatch(msg.sender, address(0), to, ids, amounts); require( to.code.length == 0 ? to != address(0) : ERC1155TokenReceiverBase(to).onERC1155BatchReceived( msg.sender, address(0), ids, amounts, data ) == ERC1155TokenReceiverBase.onERC1155BatchReceived.selector, "UNSAFE_RECIPIENT" ); } function _batchBurn( address from, uint256[] memory ids, uint256[] memory amounts ) internal virtual { uint256 idsLength = ids.length; // Saves MLOADs. require(idsLength == amounts.length, "LENGTH_MISMATCH"); for (uint256 i; i < idsLength; ) { balanceOf[from][ids[i]] -= amounts[i]; // An array can't have a total length // larger than the max uint256 value. unchecked { ++i; } } emit TransferBatch(msg.sender, from, address(0), ids, amounts); } function _burn(address from, uint256 id, uint256 amount) internal virtual { balanceOf[from][id] -= amount; emit TransferSingle(msg.sender, from, address(0), id, amount); } } /// @notice A generic interface for a contract which properly accepts ERC1155 tokens. /// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC1155.sol) abstract contract ERC1155TokenReceiverBase { function onERC1155Received( address, address, uint256, uint256, bytes calldata ) external virtual returns (bytes4) { return ERC1155TokenReceiverBase.onERC1155Received.selector; } function onERC1155BatchReceived( address, address, uint256[] calldata, uint256[] calldata, bytes calldata ) external virtual returns (bytes4) { return ERC1155TokenReceiverBase.onERC1155BatchReceived.selector; } }
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8; // Minimal ERC1155 interface. interface IERC1155 { event TransferSingle( address indexed operator, address indexed from, address indexed to, uint256 id, uint256 amount ); event TransferBatch( address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] amounts ); event ApprovalForAll(address indexed owner, address indexed operator, bool approved); function setApprovalForAll(address operator, bool approved) external; function safeTransferFrom( address from, address to, uint256 id, uint256 amount, bytes calldata data ) external; function safeBatchTransferFrom( address from, address to, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata data ) external; function balanceOf(address owner, uint256 tokenId) external view returns (uint256); function isApprovedForAll(address owner, address spender) external view returns (bool); function balanceOfBatch( address[] calldata owners, uint256[] calldata ids ) external view returns (uint256[] memory balances); }
{ "remappings": [ "forge-std/=lib/forge-std/src/", "openzeppelin/=lib/openzeppelin-contracts/", "solmate/=lib/solmate/src/", "ds-test/=lib/forge-std/lib/ds-test/src/", "erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/", "openzeppelin-contracts/=lib/openzeppelin-contracts/" ], "optimizer": { "enabled": true, "runs": 100 }, "metadata": { "useLiteralContent": false, "bytecodeHash": "ipfs", "appendCBOR": true }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } }, "evmVersion": "shanghai", "libraries": {}, "viaIR": true }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
[{"inputs":[{"internalType":"contract IGlobals","name":"globals","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"MetadataTooLarge","type":"error"},{"inputs":[{"internalType":"address","name":"caller","type":"address"},{"internalType":"address","name":"instance","type":"address"}],"name":"NotAuthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"instance","type":"address"},{"indexed":false,"internalType":"bytes","name":"metadata","type":"bytes"}],"name":"MetadataSet","type":"event"},{"inputs":[{"internalType":"address","name":"instance","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"getMetadata","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"multicallData","type":"bytes[]"}],"name":"multicall","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"instance","type":"address"},{"internalType":"bytes","name":"metadata","type":"bytes"}],"name":"setMetadata","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"supportsRegistrars","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}]
Contract Creation Code
60a03461006a57601f6117fe38819003918201601f19168301916001600160401b0383118484101761006e5780849260209460405283398101031261006a57516001600160a01b038116810361006a5760805260405161177b9081610083823960805181610a6e0152f35b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f803560e01c9081639fd8b5fa1461005757508063ac9650d814610052578063e28f5e521461004d5763f8501fec14610048575f80fd5b610228565b6101b3565b6100e4565b346100bf5760403660031901126100bf57600435610074816100c2565b602435906001600160401b03908183116100bb57366023840112156100bb5782600401359182116100bb5736602483850101116100bb5760246100b8930190610866565b80f35b8380fd5b80fd5b6001600160a01b038116036100d357565b5f80fd5b35906100e2826100c2565b565b346100d35760203660031901126100d3576004356001600160401b038082116100d357366023830112156100d35781600401358181116100d357602490600593368383871b830101116100d3579093368290036042190191905f5b86811061014857005b8481831b84010135848112156100d357830185810135908782116100d35760440181360381136100d3575f91829161018560405180938193611701565b0390305af461019261170e565b90156101ae57505f1981146101a95760010161013f565b610b8a565b61173d565b346100d3575f3660031901126100d357602060405160018152f35b5f5b8381106101df5750505f910152565b81810151838201526020016101d0565b90602091610208815180928185528580860191016101ce565b601f01601f1916010190565b9060206102259281815201906101ef565b90565b346100d35760403660031901126100d357600435610245816100c2565b61024d61040f565b6040516306fdde0360e01b815290915f826004816001600160a01b0385165afa928315610395576103476103346103649361037096610356965f91610374575b508060c0860152845261029f81611385565b8060e086015260208501526102b381611431565b60408501526102c1816114c1565b60608501526102cf81611551565b60808501526102dd816115e1565b60a08501526102eb81611671565b61010085015261032061030f610303610303846112e0565b6001600160a01b031690565b6001600160a01b0316610120860152565b61032981611317565b61014085015261134e565b61033d81610510565b610160830161051f565b60405192839160208301610538565b03601f1981018352826103ce565b60405191829182610214565b0390f35b61038f913d8091833e61038781836103ce565b810190610493565b5f61028d565b6104f1565b634e487b7160e01b5f52604160045260245ffd5b606081019081106001600160401b038211176103c957604052565b61039a565b90601f801991011681019081106001600160401b038211176103c957604052565b6040519061018082018281106001600160401b038211176103c957604052565b6040519061018082018281106001600160401b038211176103c9576040525f61016083606080825280602083015280604083015280808301528060808301528060a08301528060c08301528060e083015261010082015282610120820152826101408201520152565b6001600160401b0381116103c957601f01601f191660200190565b6020818303126100d3578051906001600160401b0382116100d3570181601f820112156100d35780516104c581610478565b926104d360405194856103ce565b818452602082840101116100d35761022591602080850191016101ce565b6040513d5f823e3d90fd5b634e487b7160e01b5f52602160045260245ffd5b6003111561051a57565b6104fc565b600382101561051a5752565b90600382101561051a5752565b6102259060208152825161055a610180918260208501526101a08401906101ef565b9361061d6105ed6105d96105c56105b161059d61058960208801519b601f199c8d8c83030160408d01526101ef565b60408801518c8b83030160608c01526101ef565b60608701518b8a83030160808b01526101ef565b60808601518a8983030160a08a01526101ef565b60a0850151898883030160c08901526101ef565b60c0840151888783030160e08801526101ef565b61060860e084015191610100928988830301848901526101ef565b908301516101209786830301888701526101ef565b948101516001600160a01b031661014084810191909152810151906101609182850152015191019061052b565b908160209103126100d35751610225816100c2565b908160209103126100d3575180151581036100d35790565b81601f820112156100d35780359061068e82610478565b9261069c60405194856103ce565b828452602083830101116100d357815f926020809301838601378301015290565b359060038210156100d357565b906020828203126100d35781356001600160401b03928382116100d3570190610180828203126100d3576106fc6103ef565b9282358181116100d35782610712918501610677565b845260208301358181116100d3578261072c918501610677565b602085015260408301358181116100d35782610749918501610677565b604085015260608301358181116100d35782610766918501610677565b606085015260808301358181116100d35782610783918501610677565b608085015260a08301358181116100d357826107a0918501610677565b60a085015260c08301358181116100d357826107bd918501610677565b60c085015260e08301358181116100d357826107da918501610677565b60e085015261010091828401359182116100d3576107f9918401610677565b9083015261012061080b8183016100d7565b9083015261014080820135908301526108286101608092016106bd565b9082015290565b6020810192916100e2919061052b565b90918060409360208452816020850152848401375f828201840152601f01601f1916010190565b92916001600160a01b038085169290338403610a50575b946101607feb93c52781456784fc831a72052881602fbd4b0251d0715f7e0852cdec233d1e9495966108b1858501856106ca565b9060208201518051610a40575b5060408201518051610a30575b5060608201518051610a20575b5060808201518051610a10575b5060a08201518051610a00575b5061010082015180516109f0575b506101208201516001600160a01b03169081166109be575b5061014081015180610994575b5001805161093281610510565b61093b81610510565b610956575b50506109516040519283928361083f565b0390a2565b61098d9161098861097a925161096b81610510565b6040519384916020830161082f565b03601f1981018452836103ce565b6112b4565b5f80610940565b6103566109b26109b892604051928391602083019190602083019252565b84611288565b5f610925565b604080516001600160a01b0390921660208301526109ea91906109e49082908101610356565b8461125c565b5f610918565b6109fa9085611165565b5f610900565b610a0a908561106e565b5f6108f2565b610a1a9085610f77565b5f6108e5565b610a2a9085610e80565b5f6108d8565b610a3a9085610d89565b5f6108cb565b610a4a9085610c33565b5f6108be565b604051635c9fcd8560e11b8152601a600482015260209081816024817f000000000000000000000000000000000000000000000000000000000000000087165afa801561039557610ad7839185935f91610b44575b5060405163ef5d39d360e01b81523360048201526001600160a01b038c16602482015293849283919082906044820190565b0392165afa918215610395575f92610b17575b505061087d5760405163c55ddc9760e01b81523360048201526001600160a01b0387166024820152604490fd5b610b369250803d10610b3d575b610b2e81836103ce565b81019061065f565b5f80610aea565b503d610b24565b610b649150833d8511610b6a575b610b5c81836103ce565b81019061064a565b5f610aa5565b503d610b52565b6001600160a01b0390911681525f602082015260400190565b634e487b7160e01b5f52601160045260245ffd5b90602082018092116101a957565b90600282018092116101a957565b90601e82018092116101a957565b90600182018092116101a957565b90600382018092116101a957565b90600482018092116101a957565b90600582018092116101a957565b906101009182039182116101a957565b919082039182116101a957565b908160031b91808304600814901517156101a957565b604051602080820182610c468583610b71565b0392610c5a601f19948581018352826103ce565b519020938181018051948251948486118015610d72575b610c90575050505050610c86610c8b91610c1d565b610c00565b1c9055565b9193969550919361ffff10610d6057610cdb6040519182610ccf898201958660405f91939293606081019460018060a01b031681528260208201520152565b039081018352826103ce565b519020600160ff1b1792839055815161ffff16905160101c9060f01b178255601e5b81519081811015610d5957610d38915f1990610d1883610b9e565b11610d3d575b610d2782610bac565b908685840101511690850155610b9e565b610cfd565b610d53610c86610d4e848751610c10565b610c1d565b1b610d1e565b5050505050565b60405163a197fa0160e01b8152600490fd5b508486148015610c71575060018760ff1c14610c71565b6040516020610dbb81830183610d9f8683610b71565b0393610db3601f19958681018352826103ce565b519020610bc8565b938181018051948251948486118015610e69575b610de4575050505050610c86610c8b91610c1d565b9193969550919361ffff10610d6057604080516001600160a01b039093168784019081525f60208201526001918101919091529190610e27908260608501610ccf565b519020600160ff1b1792839055815161ffff16905160101c9060f01b178255601e5b81519081811015610d5957610e64915f1990610d1883610b9e565b610e49565b508486148015610dcf575060018760ff1c14610dcf565b6040516020610eb281830183610e968683610b71565b0393610eaa601f19958681018352826103ce565b519020610bac565b938181018051948251948486118015610f60575b610edb575050505050610c86610c8b91610c1d565b9193969550919361ffff10610d6057604080516001600160a01b039093168784019081525f60208201526002918101919091529190610f1e908260608501610ccf565b519020600160ff1b1792839055815161ffff16905160101c9060f01b178255601e5b81519081811015610d5957610f5b915f1990610d1883610b9e565b610f40565b508486148015610ec6575060018760ff1c14610ec6565b6040516020610fa981830183610f8d8683610b71565b0393610fa1601f19958681018352826103ce565b519020610bd6565b938181018051948251948486118015611057575b610fd2575050505050610c86610c8b91610c1d565b9193969550919361ffff10610d6057604080516001600160a01b039093168784019081525f60208201526003918101919091529190611015908260608501610ccf565b519020600160ff1b1792839055815161ffff16905160101c9060f01b178255601e5b81519081811015610d5957611052915f1990610d1883610b9e565b611037565b508486148015610fbd575060018760ff1c14610fbd565b60405160206110a0818301836110848683610b71565b0393611098601f19958681018352826103ce565b519020610be4565b93818101805194825194848611801561114e575b6110c9575050505050610c86610c8b91610c1d565b9193969550919361ffff10610d6057604080516001600160a01b039093168784019081525f6020820152600491810191909152919061110c908260608501610ccf565b519020600160ff1b1792839055815161ffff16905160101c9060f01b178255601e5b81519081811015610d5957611149915f1990610d1883610b9e565b61112e565b5084861480156110b4575060018760ff1c146110b4565b60405160206111978183018361117b8683610b71565b039361118f601f19958681018352826103ce565b519020610bf2565b938181018051948251948486118015611245575b6111c0575050505050610c86610c8b91610c1d565b9193969550919361ffff10610d6057604080516001600160a01b039093168784019081525f60208201526005918101919091529190611203908260608501610ccf565b519020600160ff1b1792839055815161ffff16905160101c9060f01b178255601e5b81519081811015610d5957611240915f1990610d1883610b9e565b611225565b5084861480156111ab575060018760ff1c146111ab565b60405161127181610356602082019485610b71565b51902090600682018092116101a957602001519055565b60405161129d81610356602082019485610b71565b51902090600782018092116101a957602001519055565b6040516112c981610356602082019485610b71565b51902090600882018092116101a957602001519055565b604051602081019160018060a01b031682525f604082015260408152611305816103ae565b519020600681018091116101a9575490565b604051602081019160018060a01b031682525f60408201526040815261133c816103ae565b519020600781018091116101a9575490565b604051602081019160018060a01b031682525f604082015260408152611373816103ae565b519020600881018091116101a9575490565b604080519160209261139e816103568682019485610b71565b51902054918260ff1c156113ef5781519181845480601e86015260f01c84010190525b81518110156113e957806113d76113e492610bba565b8185015490840152610b9e565b6113c1565b50905090565b5f92835b828110611416575b5082519380855260031b610100031b90830152808201905290565b818160031b1c15611429576001016113f3565b93505f6113fb565b604061144d815192602093610db3816103568782019485610b71565b54918260ff1c156114885781519181845480601e86015260f01c84010190525b81518110156113e957806113d761148392610bba565b61146d565b5f92835b8281106114ae575082519380855260031b610100031b90830152808201905290565b818160031b1c156114295760010161148c565b60406114dd815192602093610eaa816103568782019485610b71565b54918260ff1c156115185781519181845480601e86015260f01c84010190525b81518110156113e957806113d761151392610bba565b6114fd565b5f92835b82811061153e575082519380855260031b610100031b90830152808201905290565b818160031b1c156114295760010161151c565b604061156d815192602093610fa1816103568782019485610b71565b54918260ff1c156115a85781519181845480601e86015260f01c84010190525b81518110156113e957806113d76115a392610bba565b61158d565b5f92835b8281106115ce575082519380855260031b610100031b90830152808201905290565b818160031b1c15611429576001016115ac565b60406115fd815192602093611098816103568782019485610b71565b54918260ff1c156116385781519181845480601e86015260f01c84010190525b81518110156113e957806113d761163392610bba565b61161d565b5f92835b82811061165e575082519380855260031b610100031b90830152808201905290565b818160031b1c156114295760010161163c565b604061168d81519260209361118f816103568782019485610b71565b54918260ff1c156116c85781519181845480601e86015260f01c84010190525b81518110156113e957806113d76116c392610bba565b6116ad565b5f92835b8281106116ee575082519380855260031b610100031b90830152808201905290565b818160031b1c15611429576001016116cc565b908092918237015f815290565b3d15611738573d9061171f82610478565b9161172d60405193846103ce565b82523d5f602084013e565b606090565b602081519101fdfea26469706673582212202431600c12897116883bf2d4d9efdbed042462cbd72793d0089d96bacf623c1864736f6c634300081400330000000000000000000000001ca20040ce6ad406bc2a6c89976388829e7fbade
Deployed Bytecode
0x60806040526004361015610011575f80fd5b5f803560e01c9081639fd8b5fa1461005757508063ac9650d814610052578063e28f5e521461004d5763f8501fec14610048575f80fd5b610228565b6101b3565b6100e4565b346100bf5760403660031901126100bf57600435610074816100c2565b602435906001600160401b03908183116100bb57366023840112156100bb5782600401359182116100bb5736602483850101116100bb5760246100b8930190610866565b80f35b8380fd5b80fd5b6001600160a01b038116036100d357565b5f80fd5b35906100e2826100c2565b565b346100d35760203660031901126100d3576004356001600160401b038082116100d357366023830112156100d35781600401358181116100d357602490600593368383871b830101116100d3579093368290036042190191905f5b86811061014857005b8481831b84010135848112156100d357830185810135908782116100d35760440181360381136100d3575f91829161018560405180938193611701565b0390305af461019261170e565b90156101ae57505f1981146101a95760010161013f565b610b8a565b61173d565b346100d3575f3660031901126100d357602060405160018152f35b5f5b8381106101df5750505f910152565b81810151838201526020016101d0565b90602091610208815180928185528580860191016101ce565b601f01601f1916010190565b9060206102259281815201906101ef565b90565b346100d35760403660031901126100d357600435610245816100c2565b61024d61040f565b6040516306fdde0360e01b815290915f826004816001600160a01b0385165afa928315610395576103476103346103649361037096610356965f91610374575b508060c0860152845261029f81611385565b8060e086015260208501526102b381611431565b60408501526102c1816114c1565b60608501526102cf81611551565b60808501526102dd816115e1565b60a08501526102eb81611671565b61010085015261032061030f610303610303846112e0565b6001600160a01b031690565b6001600160a01b0316610120860152565b61032981611317565b61014085015261134e565b61033d81610510565b610160830161051f565b60405192839160208301610538565b03601f1981018352826103ce565b60405191829182610214565b0390f35b61038f913d8091833e61038781836103ce565b810190610493565b5f61028d565b6104f1565b634e487b7160e01b5f52604160045260245ffd5b606081019081106001600160401b038211176103c957604052565b61039a565b90601f801991011681019081106001600160401b038211176103c957604052565b6040519061018082018281106001600160401b038211176103c957604052565b6040519061018082018281106001600160401b038211176103c9576040525f61016083606080825280602083015280604083015280808301528060808301528060a08301528060c08301528060e083015261010082015282610120820152826101408201520152565b6001600160401b0381116103c957601f01601f191660200190565b6020818303126100d3578051906001600160401b0382116100d3570181601f820112156100d35780516104c581610478565b926104d360405194856103ce565b818452602082840101116100d35761022591602080850191016101ce565b6040513d5f823e3d90fd5b634e487b7160e01b5f52602160045260245ffd5b6003111561051a57565b6104fc565b600382101561051a5752565b90600382101561051a5752565b6102259060208152825161055a610180918260208501526101a08401906101ef565b9361061d6105ed6105d96105c56105b161059d61058960208801519b601f199c8d8c83030160408d01526101ef565b60408801518c8b83030160608c01526101ef565b60608701518b8a83030160808b01526101ef565b60808601518a8983030160a08a01526101ef565b60a0850151898883030160c08901526101ef565b60c0840151888783030160e08801526101ef565b61060860e084015191610100928988830301848901526101ef565b908301516101209786830301888701526101ef565b948101516001600160a01b031661014084810191909152810151906101609182850152015191019061052b565b908160209103126100d35751610225816100c2565b908160209103126100d3575180151581036100d35790565b81601f820112156100d35780359061068e82610478565b9261069c60405194856103ce565b828452602083830101116100d357815f926020809301838601378301015290565b359060038210156100d357565b906020828203126100d35781356001600160401b03928382116100d3570190610180828203126100d3576106fc6103ef565b9282358181116100d35782610712918501610677565b845260208301358181116100d3578261072c918501610677565b602085015260408301358181116100d35782610749918501610677565b604085015260608301358181116100d35782610766918501610677565b606085015260808301358181116100d35782610783918501610677565b608085015260a08301358181116100d357826107a0918501610677565b60a085015260c08301358181116100d357826107bd918501610677565b60c085015260e08301358181116100d357826107da918501610677565b60e085015261010091828401359182116100d3576107f9918401610677565b9083015261012061080b8183016100d7565b9083015261014080820135908301526108286101608092016106bd565b9082015290565b6020810192916100e2919061052b565b90918060409360208452816020850152848401375f828201840152601f01601f1916010190565b92916001600160a01b038085169290338403610a50575b946101607feb93c52781456784fc831a72052881602fbd4b0251d0715f7e0852cdec233d1e9495966108b1858501856106ca565b9060208201518051610a40575b5060408201518051610a30575b5060608201518051610a20575b5060808201518051610a10575b5060a08201518051610a00575b5061010082015180516109f0575b506101208201516001600160a01b03169081166109be575b5061014081015180610994575b5001805161093281610510565b61093b81610510565b610956575b50506109516040519283928361083f565b0390a2565b61098d9161098861097a925161096b81610510565b6040519384916020830161082f565b03601f1981018452836103ce565b6112b4565b5f80610940565b6103566109b26109b892604051928391602083019190602083019252565b84611288565b5f610925565b604080516001600160a01b0390921660208301526109ea91906109e49082908101610356565b8461125c565b5f610918565b6109fa9085611165565b5f610900565b610a0a908561106e565b5f6108f2565b610a1a9085610f77565b5f6108e5565b610a2a9085610e80565b5f6108d8565b610a3a9085610d89565b5f6108cb565b610a4a9085610c33565b5f6108be565b604051635c9fcd8560e11b8152601a600482015260209081816024817f0000000000000000000000001ca20040ce6ad406bc2a6c89976388829e7fbade87165afa801561039557610ad7839185935f91610b44575b5060405163ef5d39d360e01b81523360048201526001600160a01b038c16602482015293849283919082906044820190565b0392165afa918215610395575f92610b17575b505061087d5760405163c55ddc9760e01b81523360048201526001600160a01b0387166024820152604490fd5b610b369250803d10610b3d575b610b2e81836103ce565b81019061065f565b5f80610aea565b503d610b24565b610b649150833d8511610b6a575b610b5c81836103ce565b81019061064a565b5f610aa5565b503d610b52565b6001600160a01b0390911681525f602082015260400190565b634e487b7160e01b5f52601160045260245ffd5b90602082018092116101a957565b90600282018092116101a957565b90601e82018092116101a957565b90600182018092116101a957565b90600382018092116101a957565b90600482018092116101a957565b90600582018092116101a957565b906101009182039182116101a957565b919082039182116101a957565b908160031b91808304600814901517156101a957565b604051602080820182610c468583610b71565b0392610c5a601f19948581018352826103ce565b519020938181018051948251948486118015610d72575b610c90575050505050610c86610c8b91610c1d565b610c00565b1c9055565b9193969550919361ffff10610d6057610cdb6040519182610ccf898201958660405f91939293606081019460018060a01b031681528260208201520152565b039081018352826103ce565b519020600160ff1b1792839055815161ffff16905160101c9060f01b178255601e5b81519081811015610d5957610d38915f1990610d1883610b9e565b11610d3d575b610d2782610bac565b908685840101511690850155610b9e565b610cfd565b610d53610c86610d4e848751610c10565b610c1d565b1b610d1e565b5050505050565b60405163a197fa0160e01b8152600490fd5b508486148015610c71575060018760ff1c14610c71565b6040516020610dbb81830183610d9f8683610b71565b0393610db3601f19958681018352826103ce565b519020610bc8565b938181018051948251948486118015610e69575b610de4575050505050610c86610c8b91610c1d565b9193969550919361ffff10610d6057604080516001600160a01b039093168784019081525f60208201526001918101919091529190610e27908260608501610ccf565b519020600160ff1b1792839055815161ffff16905160101c9060f01b178255601e5b81519081811015610d5957610e64915f1990610d1883610b9e565b610e49565b508486148015610dcf575060018760ff1c14610dcf565b6040516020610eb281830183610e968683610b71565b0393610eaa601f19958681018352826103ce565b519020610bac565b938181018051948251948486118015610f60575b610edb575050505050610c86610c8b91610c1d565b9193969550919361ffff10610d6057604080516001600160a01b039093168784019081525f60208201526002918101919091529190610f1e908260608501610ccf565b519020600160ff1b1792839055815161ffff16905160101c9060f01b178255601e5b81519081811015610d5957610f5b915f1990610d1883610b9e565b610f40565b508486148015610ec6575060018760ff1c14610ec6565b6040516020610fa981830183610f8d8683610b71565b0393610fa1601f19958681018352826103ce565b519020610bd6565b938181018051948251948486118015611057575b610fd2575050505050610c86610c8b91610c1d565b9193969550919361ffff10610d6057604080516001600160a01b039093168784019081525f60208201526003918101919091529190611015908260608501610ccf565b519020600160ff1b1792839055815161ffff16905160101c9060f01b178255601e5b81519081811015610d5957611052915f1990610d1883610b9e565b611037565b508486148015610fbd575060018760ff1c14610fbd565b60405160206110a0818301836110848683610b71565b0393611098601f19958681018352826103ce565b519020610be4565b93818101805194825194848611801561114e575b6110c9575050505050610c86610c8b91610c1d565b9193969550919361ffff10610d6057604080516001600160a01b039093168784019081525f6020820152600491810191909152919061110c908260608501610ccf565b519020600160ff1b1792839055815161ffff16905160101c9060f01b178255601e5b81519081811015610d5957611149915f1990610d1883610b9e565b61112e565b5084861480156110b4575060018760ff1c146110b4565b60405160206111978183018361117b8683610b71565b039361118f601f19958681018352826103ce565b519020610bf2565b938181018051948251948486118015611245575b6111c0575050505050610c86610c8b91610c1d565b9193969550919361ffff10610d6057604080516001600160a01b039093168784019081525f60208201526005918101919091529190611203908260608501610ccf565b519020600160ff1b1792839055815161ffff16905160101c9060f01b178255601e5b81519081811015610d5957611240915f1990610d1883610b9e565b611225565b5084861480156111ab575060018760ff1c146111ab565b60405161127181610356602082019485610b71565b51902090600682018092116101a957602001519055565b60405161129d81610356602082019485610b71565b51902090600782018092116101a957602001519055565b6040516112c981610356602082019485610b71565b51902090600882018092116101a957602001519055565b604051602081019160018060a01b031682525f604082015260408152611305816103ae565b519020600681018091116101a9575490565b604051602081019160018060a01b031682525f60408201526040815261133c816103ae565b519020600781018091116101a9575490565b604051602081019160018060a01b031682525f604082015260408152611373816103ae565b519020600881018091116101a9575490565b604080519160209261139e816103568682019485610b71565b51902054918260ff1c156113ef5781519181845480601e86015260f01c84010190525b81518110156113e957806113d76113e492610bba565b8185015490840152610b9e565b6113c1565b50905090565b5f92835b828110611416575b5082519380855260031b610100031b90830152808201905290565b818160031b1c15611429576001016113f3565b93505f6113fb565b604061144d815192602093610db3816103568782019485610b71565b54918260ff1c156114885781519181845480601e86015260f01c84010190525b81518110156113e957806113d761148392610bba565b61146d565b5f92835b8281106114ae575082519380855260031b610100031b90830152808201905290565b818160031b1c156114295760010161148c565b60406114dd815192602093610eaa816103568782019485610b71565b54918260ff1c156115185781519181845480601e86015260f01c84010190525b81518110156113e957806113d761151392610bba565b6114fd565b5f92835b82811061153e575082519380855260031b610100031b90830152808201905290565b818160031b1c156114295760010161151c565b604061156d815192602093610fa1816103568782019485610b71565b54918260ff1c156115a85781519181845480601e86015260f01c84010190525b81518110156113e957806113d76115a392610bba565b61158d565b5f92835b8281106115ce575082519380855260031b610100031b90830152808201905290565b818160031b1c15611429576001016115ac565b60406115fd815192602093611098816103568782019485610b71565b54918260ff1c156116385781519181845480601e86015260f01c84010190525b81518110156113e957806113d761163392610bba565b61161d565b5f92835b82811061165e575082519380855260031b610100031b90830152808201905290565b818160031b1c156114295760010161163c565b604061168d81519260209361118f816103568782019485610b71565b54918260ff1c156116c85781519181845480601e86015260f01c84010190525b81518110156113e957806113d76116c392610bba565b6116ad565b5f92835b8281106116ee575082519380855260031b610100031b90830152808201905290565b818160031b1c15611429576001016116cc565b908092918237015f815290565b3d15611738573d9061171f82610478565b9161172d60405193846103ce565b82523d5f602084013e565b606090565b602081519101fdfea26469706673582212202431600c12897116883bf2d4d9efdbed042462cbd72793d0089d96bacf623c1864736f6c63430008140033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
0000000000000000000000001ca20040ce6ad406bc2a6c89976388829e7fbade
-----Decoded View---------------
Arg [0] : globals (address): 0x1cA20040cE6aD406bC2A6c89976388829E7fbAde
-----Encoded View---------------
1 Constructor Arguments found :
Arg [0] : 0000000000000000000000001ca20040ce6ad406bc2a6c89976388829e7fbade
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 26 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|
Loading...
Loading
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.