Transaction Hash:
Block:
18530469 at Nov-08-2023 11:18:35 PM +UTC
Transaction Fee:
0.004894257484373938 ETH
$8.83
Gas Used:
133,414 Gas / 36.684736867 Gwei
Emitted Events:
418 |
GenArt721CoreV3_Engine_Flex.Transfer( from=0x00000000...000000000, to=0xAb2c20Cc...F32403Aa4, tokenId=1000042 )
|
419 |
GenArt721CoreV3_Engine_Flex.Mint( _to=0xAb2c20Cc...F32403Aa4, _tokenId=1000042 )
|
420 |
GnosisSafeProxy.0x3d0ce9bfc3ed7d6862dbb28b2dea94561fe714a1b4d019aa8af39730d1ad7c3d( 0x3d0ce9bfc3ed7d6862dbb28b2dea94561fe714a1b4d019aa8af39730d1ad7c3d, 0x0000000000000000000000003ade999397f6be92a637acdcbfb875c63f4f96de, 000000000000000000000000000000000000000000000000011c37937e080000 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x5fdf5E6C...07A7e1389 | |||||
0x8F67Bfd9...637AcaA8B |
1.317502413108010686 Eth
Nonce: 56
|
1.112608155623636748 Eth
Nonce: 57
| 0.204894257484373938 | ||
0xb24EDdc1...CAa0AC105 | 3.880049064794848128 Eth | 3.960049064794848128 Eth | 0.08 | ||
0xbe61F1DE...702f67e54 | 4.298011656721060907 Eth | 4.398011656721060907 Eth | 0.1 | ||
0xD824b37d...654686a26
Miner
| 32.759742523157686331 Eth | 32.759755864557686331 Eth | 0.0000133414 | ||
0xFc7C0b52...1A56f2ac1 | 0.84 Eth | 0.86 Eth | 0.02 |
Execution Trace
ETH 0.2
MinterSetPriceV4.purchaseTo( _to=0xAb2c20Cc9365c6A79B6A26040bB0B94F32403Aa4, _projectId=1 ) => ( tokenId=1000042 )
MinterFilterV1.mint( _to=0xAb2c20Cc9365c6A79B6A26040bB0B94F32403Aa4, _projectId=1, sender=0x8F67Bfd95a311fbc4e6e95AdF43B077637AcaA8B ) => ( _tokenId=1000042 )
GenArt721CoreV3_Engine_Flex.mint_Ecf( _to=0xAb2c20Cc9365c6A79B6A26040bB0B94F32403Aa4, _projectId=1, _by=0x8F67Bfd95a311fbc4e6e95AdF43B077637AcaA8B ) => ( _tokenId=1000042 )
0xd375529b79f2525c058a935d88e95e230334de33.b628171a( )
-
GenArt721CoreV3_Engine_Flex.setTokenHash_8PT( _tokenId=1000042, _hashSeed=D4759B1029D36B0779A7EEFADA6A2628E47DAE720C7E55608492EF28B7A0D49B )
-
-
GenArt721CoreV3_Engine_Flex.getPrimaryRevenueSplits( _projectId=1, _price=200000000000000000 ) => ( renderProviderRevenue_=20000000000000000, renderProviderAddress_=0xFc7C0b521ed8397E4625842eb00828c1A56f2ac1, platformProviderRevenue_=80000000000000000, platformProviderAddress_=0xb24EDdc13d67FEa233D3810f7A2426ACAa0AC105, artistRevenue_=0, artistAddress_=0x0000000000000000000000000000000000000000, additionalPayeePrimaryRevenue_=100000000000000000, additionalPayeePrimaryAddress_=0xbe61F1DE5A50E219eD5E661DA82766E702f67e54 )
ETH 0.08
GnosisSafeProxy.CALL( )
- ETH 0.08
GnosisSafe.DELEGATECALL( )
- ETH 0.08
- ETH 0.02
0xfc7c0b521ed8397e4625842eb00828c1a56f2ac1.CALL( )
- ETH 0.1
0xbe61f1de5a50e219ed5e661da82766e702f67e54.CALL( )
purchaseTo[MinterSetPriceV4 (ln:1250)]
purchaseTo_do6[MinterSetPriceV4 (ln:1254)]
mint[MinterSetPriceV4 (ln:1284)]
splitFundsETH[MinterSetPriceV4 (ln:1308)]
File 1 of 5: MinterSetPriceV4
File 2 of 5: GenArt721CoreV3_Engine_Flex
File 3 of 5: GnosisSafeProxy
File 4 of 5: MinterFilterV1
File 5 of 5: GnosisSafe
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol) pragma solidity ^0.8.0; /** * @dev Contract module that helps prevent reentrant calls to a function. * * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier * available, which can be applied to functions to make sure there are no nested * (reentrant) calls to them. * * Note that because there is a single `nonReentrant` guard, functions marked as * `nonReentrant` may not call one another. This can be worked around by making * those functions `private`, and then adding `external` `nonReentrant` entry * points to them. * * TIP: If you would like to learn more about reentrancy and alternative ways * to protect against it, check out our blog post * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. */ abstract contract ReentrancyGuard { // Booleans are more expensive than uint256 or any type that takes up a full // word because each write operation emits an extra SLOAD to first read the // slot's contents, replace the bits taken up by the boolean, and then write // back. This is the compiler's defense against contract upgrades and // pointer aliasing, and it cannot be disabled. // The values being non-zero value makes deployment a bit more expensive, // but in exchange the refund on every call to nonReentrant will be lower in // amount. Since refunds are capped to a percentage of the total // transaction's gas, it is best to keep them low in cases like this one, to // increase the likelihood of the full refund coming into effect. uint256 private constant _NOT_ENTERED = 1; uint256 private constant _ENTERED = 2; uint256 private _status; constructor() { _status = _NOT_ENTERED; } /** * @dev Prevents a contract from calling itself, directly or indirectly. * Calling a `nonReentrant` function from another `nonReentrant` * function is not supported. It is possible to prevent this from happening * by making the `nonReentrant` function external, and making it call a * `private` function that does the actual work. */ modifier nonReentrant() { // On the first call to nonReentrant, _notEntered will be true require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); // Any calls to nonReentrant after this point will fail _status = _ENTERED; _; // By storing the original value once again, a refund is triggered (see // https://eips.ethereum.org/EIPS/eip-2200) _status = _NOT_ENTERED; } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol) pragma solidity ^0.8.0; /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IERC20 { /** * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */ event Transfer(address indexed from, address indexed to, uint256 value); /** * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); /** * @dev Returns the amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the amount of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves `amount` tokens from the caller's account to `to`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address to, uint256 amount) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */ function approve(address spender, uint256 amount) external returns (bool); /** * @dev Moves `amount` tokens from `from` to `to` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom( address from, address to, uint256 amount ) external returns (bool); } // SPDX-License-Identifier: LGPL-3.0-only // Created By: Art Blocks Inc. pragma solidity ^0.8.0; interface IAdminACLV0 { /** * @notice Token ID `_tokenId` minted to `_to`. * @param previousSuperAdmin The previous superAdmin address. * @param newSuperAdmin The new superAdmin address. * @param genArt721CoreAddressesToUpdate Array of genArt721Core * addresses to update to the new superAdmin, for indexing purposes only. */ event SuperAdminTransferred( address indexed previousSuperAdmin, address indexed newSuperAdmin, address[] genArt721CoreAddressesToUpdate ); /// Type of the Admin ACL contract, e.g. "AdminACLV0" function AdminACLType() external view returns (string memory); /// super admin address function superAdmin() external view returns (address); /** * @notice Calls transferOwnership on other contract from this contract. * This is useful for updating to a new AdminACL contract. * @dev this function should be gated to only superAdmin-like addresses. */ function transferOwnershipOn( address _contract, address _newAdminACL ) external; /** * @notice Calls renounceOwnership on other contract from this contract. * @dev this function should be gated to only superAdmin-like addresses. */ function renounceOwnershipOn(address _contract) external; /** * @notice Checks if sender `_sender` is allowed to call function with selector * `_selector` on contract `_contract`. */ function allowed( address _sender, address _contract, bytes4 _selector ) external returns (bool); } // SPDX-License-Identifier: LGPL-3.0-only // Created By: Art Blocks Inc. pragma solidity ^0.8.0; interface IFilteredMinterV0 { /** * @notice Price per token in wei updated for project `_projectId` to * `_pricePerTokenInWei`. */ event PricePerTokenInWeiUpdated( uint256 indexed _projectId, uint256 indexed _pricePerTokenInWei ); /** * @notice Currency updated for project `_projectId` to symbol * `_currencySymbol` and address `_currencyAddress`. */ event ProjectCurrencyInfoUpdated( uint256 indexed _projectId, address indexed _currencyAddress, string _currencySymbol ); /// togglePurchaseToDisabled updated event PurchaseToDisabledUpdated( uint256 indexed _projectId, bool _purchaseToDisabled ); // getter function of public variable function minterType() external view returns (string memory); function genArt721CoreAddress() external returns (address); function minterFilterAddress() external returns (address); // Triggers a purchase of a token from the desired project, to the // TX-sending address. function purchase( uint256 _projectId ) external payable returns (uint256 tokenId); // Triggers a purchase of a token from the desired project, to the specified // receiving address. function purchaseTo( address _to, uint256 _projectId ) external payable returns (uint256 tokenId); // Toggles the ability for `purchaseTo` to be called directly with a // specified receiving address that differs from the TX-sending address. function togglePurchaseToDisabled(uint256 _projectId) external; // Called to make the minter contract aware of the max invocations for a // given project. function setProjectMaxInvocations(uint256 _projectId) external; // Gets if token price is configured, token price in wei, currency symbol, // and currency address, assuming this is project's minter. // Supersedes any defined core price. function getPriceInfo( uint256 _projectId ) external view returns ( bool isConfigured, uint256 tokenPriceInWei, string memory currencySymbol, address currencyAddress ); } // SPDX-License-Identifier: LGPL-3.0-only // Created By: Art Blocks Inc. import "./IFilteredMinterV0.sol"; pragma solidity ^0.8.0; /** * @title This interface extends the IFilteredMinterV0 interface in order to * add support for generic project minter configuration updates. * @dev keys represent strings of finite length encoded in bytes32 to minimize * gas. * @author Art Blocks Inc. */ interface IFilteredMinterV1 is IFilteredMinterV0 { /// ANY /** * @notice Generic project minter configuration event. Removes key `_key` * for project `_projectId`. */ event ConfigKeyRemoved(uint256 indexed _projectId, bytes32 _key); /// BOOL /** * @notice Generic project minter configuration event. Sets value of key * `_key` to `_value` for project `_projectId`. */ event ConfigValueSet(uint256 indexed _projectId, bytes32 _key, bool _value); /// UINT256 /** * @notice Generic project minter configuration event. Sets value of key * `_key` to `_value` for project `_projectId`. */ event ConfigValueSet( uint256 indexed _projectId, bytes32 _key, uint256 _value ); /** * @notice Generic project minter configuration event. Adds value `_value` * to the set of uint256 at key `_key` for project `_projectId`. */ event ConfigValueAddedToSet( uint256 indexed _projectId, bytes32 _key, uint256 _value ); /** * @notice Generic project minter configuration event. Removes value * `_value` to the set of uint256 at key `_key` for project `_projectId`. */ event ConfigValueRemovedFromSet( uint256 indexed _projectId, bytes32 _key, uint256 _value ); /// ADDRESS /** * @notice Generic project minter configuration event. Sets value of key * `_key` to `_value` for project `_projectId`. */ event ConfigValueSet( uint256 indexed _projectId, bytes32 _key, address _value ); /** * @notice Generic project minter configuration event. Adds value `_value` * to the set of addresses at key `_key` for project `_projectId`. */ event ConfigValueAddedToSet( uint256 indexed _projectId, bytes32 _key, address _value ); /** * @notice Generic project minter configuration event. Removes value * `_value` to the set of addresses at key `_key` for project `_projectId`. */ event ConfigValueRemovedFromSet( uint256 indexed _projectId, bytes32 _key, address _value ); /// BYTES32 /** * @notice Generic project minter configuration event. Sets value of key * `_key` to `_value` for project `_projectId`. */ event ConfigValueSet( uint256 indexed _projectId, bytes32 _key, bytes32 _value ); /** * @notice Generic project minter configuration event. Adds value `_value` * to the set of bytes32 at key `_key` for project `_projectId`. */ event ConfigValueAddedToSet( uint256 indexed _projectId, bytes32 _key, bytes32 _value ); /** * @notice Generic project minter configuration event. Removes value * `_value` to the set of bytes32 at key `_key` for project `_projectId`. */ event ConfigValueRemovedFromSet( uint256 indexed _projectId, bytes32 _key, bytes32 _value ); /** * @dev Strings not supported. Recommend conversion of (short) strings to * bytes32 to remain gas-efficient. */ } // SPDX-License-Identifier: LGPL-3.0-only // Created By: Art Blocks Inc. import "./IFilteredMinterV1.sol"; pragma solidity ^0.8.0; /** * @title This interface extends the IFilteredMinterV1 interface in order to * add support for manually setting project max invocations. * @author Art Blocks Inc. */ interface IFilteredMinterV2 is IFilteredMinterV1 { /** * @notice Local max invocations for project `_projectId`, tied to core contract `_coreContractAddress`, * updated to `_maxInvocations`. */ event ProjectMaxInvocationsLimitUpdated( uint256 indexed _projectId, uint256 _maxInvocations ); // Sets the local max invocations for a given project, checking that the provided max invocations is // less than or equal to the global max invocations for the project set on the core contract. // This does not impact the max invocations value defined on the core contract. function manuallyLimitProjectMaxInvocations( uint256 _projectId, uint256 _maxInvocations ) external; } // SPDX-License-Identifier: LGPL-3.0-only // Created By: Art Blocks Inc. pragma solidity ^0.8.0; import "./IAdminACLV0.sol"; /// use the Royalty Registry's IManifold interface for token royalties import "./IManifold.sol"; /** * @title This interface is intended to house interface items that are common * across all GenArt721CoreContractV3 flagship and derivative implementations. * This interface extends the IManifold royalty interface in order to * add support the Royalty Registry by default. * @author Art Blocks Inc. */ interface IGenArt721CoreContractV3_Base is IManifold { /** * @notice Token ID `_tokenId` minted to `_to`. */ event Mint(address indexed _to, uint256 indexed _tokenId); /** * @notice currentMinter updated to `_currentMinter`. * @dev Implemented starting with V3 core */ event MinterUpdated(address indexed _currentMinter); /** * @notice Platform updated on bytes32-encoded field `_field`. */ event PlatformUpdated(bytes32 indexed _field); /** * @notice Project ID `_projectId` updated on bytes32-encoded field * `_update`. */ event ProjectUpdated(uint256 indexed _projectId, bytes32 indexed _update); event ProposedArtistAddressesAndSplits( uint256 indexed _projectId, address _artistAddress, address _additionalPayeePrimarySales, uint256 _additionalPayeePrimarySalesPercentage, address _additionalPayeeSecondarySales, uint256 _additionalPayeeSecondarySalesPercentage ); event AcceptedArtistAddressesAndSplits(uint256 indexed _projectId); // version and type of the core contract // coreVersion is a string of the form "0.x.y" function coreVersion() external view returns (string memory); // coreType is a string of the form "GenArt721CoreV3" function coreType() external view returns (string memory); // owner (pre-V3 was named admin) of contract // this is expected to be an Admin ACL contract for V3 function owner() external view returns (address); // Admin ACL contract for V3, will be at the address owner() function adminACLContract() external returns (IAdminACLV0); // backwards-compatible (pre-V3) admin - equal to owner() function admin() external view returns (address); /** * Function determining if _sender is allowed to call function with * selector _selector on contract `_contract`. Intended to be used with * peripheral contracts such as minters, as well as internally by the * core contract itself. */ function adminACLAllowed( address _sender, address _contract, bytes4 _selector ) external returns (bool); /// getter function of public variable function startingProjectId() external view returns (uint256); // getter function of public variable function nextProjectId() external view returns (uint256); // getter function of public mapping function tokenIdToProjectId( uint256 tokenId ) external view returns (uint256 projectId); // @dev this is not available in V0 function isMintWhitelisted(address minter) external view returns (bool); function projectIdToArtistAddress( uint256 _projectId ) external view returns (address payable); function projectIdToAdditionalPayeePrimarySales( uint256 _projectId ) external view returns (address payable); function projectIdToAdditionalPayeePrimarySalesPercentage( uint256 _projectId ) external view returns (uint256); function projectIdToSecondaryMarketRoyaltyPercentage( uint256 _projectId ) external view returns (uint256); function projectURIInfo( uint256 _projectId ) external view returns (string memory projectBaseURI); // @dev new function in V3 function projectStateData( uint256 _projectId ) external view returns ( uint256 invocations, uint256 maxInvocations, bool active, bool paused, uint256 completedTimestamp, bool locked ); function projectDetails( uint256 _projectId ) external view returns ( string memory projectName, string memory artist, string memory description, string memory website, string memory license ); function projectScriptDetails( uint256 _projectId ) external view returns ( string memory scriptTypeAndVersion, string memory aspectRatio, uint256 scriptCount ); function projectScriptByIndex( uint256 _projectId, uint256 _index ) external view returns (string memory); function tokenIdToHash(uint256 _tokenId) external view returns (bytes32); // function to set a token's hash (must be guarded) function setTokenHash_8PT(uint256 _tokenId, bytes32 _hash) external; // @dev gas-optimized signature in V3 for `mint` function mint_Ecf( address _to, uint256 _projectId, address _by ) external returns (uint256 tokenId); } // SPDX-License-Identifier: LGPL-3.0-only // Created By: Art Blocks Inc. pragma solidity ^0.8.0; import "./IAdminACLV0.sol"; import "./IGenArt721CoreContractV3_Base.sol"; interface IGenArt721CoreContractV3_Engine is IGenArt721CoreContractV3_Base { // @dev new function in V3 function getPrimaryRevenueSplits( uint256 _projectId, uint256 _price ) external view returns ( uint256 renderProviderRevenue_, address payable renderProviderAddress_, uint256 platformProviderRevenue_, address payable platformProviderAddress_, uint256 artistRevenue_, address payable artistAddress_, uint256 additionalPayeePrimaryRevenue_, address payable additionalPayeePrimaryAddress_ ); // @dev The render provider primary sales payment address function renderProviderPrimarySalesAddress() external view returns (address payable); // @dev The platform provider primary sales payment address function platformProviderPrimarySalesAddress() external view returns (address payable); // @dev Percentage of primary sales allocated to the render provider function renderProviderPrimarySalesPercentage() external view returns (uint256); // @dev Percentage of primary sales allocated to the platform provider function platformProviderPrimarySalesPercentage() external view returns (uint256); // @dev The render provider secondary sales royalties payment address function renderProviderSecondarySalesAddress() external view returns (address payable); // @dev The platform provider secondary sales royalties payment address function platformProviderSecondarySalesAddress() external view returns (address payable); // @dev Basis points of secondary sales allocated to the render provider function renderProviderSecondarySalesBPS() external view returns (uint256); // @dev Basis points of secondary sales allocated to the platform provider function platformProviderSecondarySalesBPS() external view returns (uint256); } // SPDX-License-Identifier: LGPL-3.0-only // Created By: Art Blocks Inc. pragma solidity ^0.8.0; import "./IAdminACLV0.sol"; import "./IGenArt721CoreContractV3_Base.sol"; /** * @title This interface extends IGenArt721CoreContractV3_Base with functions * that are part of the Art Blocks Flagship core contract. * @author Art Blocks Inc. */ // This interface extends IGenArt721CoreContractV3_Base with functions that are // in part of the Art Blocks Flagship core contract. interface IGenArt721CoreContractV3 is IGenArt721CoreContractV3_Base { // @dev new function in V3 function getPrimaryRevenueSplits( uint256 _projectId, uint256 _price ) external view returns ( uint256 artblocksRevenue_, address payable artblocksAddress_, uint256 artistRevenue_, address payable artistAddress_, uint256 additionalPayeePrimaryRevenue_, address payable additionalPayeePrimaryAddress_ ); // @dev Art Blocks primary sales payment address function artblocksPrimarySalesAddress() external view returns (address payable); /** * @notice Backwards-compatible (pre-V3) function returning Art Blocks * primary sales payment address (now called artblocksPrimarySalesAddress). */ function artblocksAddress() external view returns (address payable); // @dev Percentage of primary sales allocated to Art Blocks function artblocksPrimarySalesPercentage() external view returns (uint256); /** * @notice Backwards-compatible (pre-V3) function returning Art Blocks * primary sales percentage (now called artblocksPrimarySalesPercentage). */ function artblocksPercentage() external view returns (uint256); // @dev Art Blocks secondary sales royalties payment address function artblocksSecondarySalesAddress() external view returns (address payable); // @dev Basis points of secondary sales allocated to Art Blocks function artblocksSecondarySalesBPS() external view returns (uint256); /** * @notice Backwards-compatible (pre-V3) function that gets artist + * artist's additional payee royalty data for token ID `_tokenId`. * WARNING: Does not include Art Blocks portion of royalties. */ function getRoyaltyData( uint256 _tokenId ) external view returns ( address artistAddress, address additionalPayee, uint256 additionalPayeePercentage, uint256 royaltyFeeByID ); } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /// @dev Royalty Registry interface, used to support the Royalty Registry. /// @dev Source: https://github.com/manifoldxyz/royalty-registry-solidity/blob/main/contracts/specs/IManifold.sol /// @author: manifold.xyz /** * @dev Royalty interface for creator core classes */ interface IManifold { /** * @dev Get royalites of a token. Returns list of receivers and basisPoints * * bytes4(keccak256('getRoyalties(uint256)')) == 0xbb3bafd6 * * => 0xbb3bafd6 = 0xbb3bafd6 */ function getRoyalties( uint256 tokenId ) external view returns (address payable[] memory, uint256[] memory); } // SPDX-License-Identifier: LGPL-3.0-only // Created By: Art Blocks Inc. import "./IFilteredMinterV2.sol"; pragma solidity ^0.8.0; /** * @title This interface defines any events or functions required for a minter * to conform to the MinterBase contract. * @dev The MinterBase contract was not implemented from the beginning of the * MinterSuite contract suite, therefore early versions of some minters may not * conform to this interface. * @author Art Blocks Inc. */ interface IMinterBaseV0 { // Function that returns if a minter is configured to integrate with a V3 flagship or V3 engine contract. // Returns true only if the minter is configured to integrate with an engine contract. function isEngine() external returns (bool isEngine); } // SPDX-License-Identifier: LGPL-3.0-only // Created By: Art Blocks Inc. pragma solidity ^0.8.0; interface IMinterFilterV0 { /** * @notice Emitted when contract is deployed to notify indexing services * of the new contract deployment. */ event Deployed(); /** * @notice Approved minter `_minterAddress`. */ event MinterApproved(address indexed _minterAddress, string _minterType); /** * @notice Revoked approval for minter `_minterAddress` */ event MinterRevoked(address indexed _minterAddress); /** * @notice Minter `_minterAddress` of type `_minterType` * registered for project `_projectId`. */ event ProjectMinterRegistered( uint256 indexed _projectId, address indexed _minterAddress, string _minterType ); /** * @notice Any active minter removed for project `_projectId`. */ event ProjectMinterRemoved(uint256 indexed _projectId); function genArt721CoreAddress() external returns (address); function setMinterForProject(uint256, address) external; function removeMinterForProject(uint256) external; function mint( address _to, uint256 _projectId, address sender ) external returns (uint256); function getMinterForProject(uint256) external view returns (address); function projectHasMinter(uint256) external view returns (bool); } // SPDX-License-Identifier: LGPL-3.0-only // Created By: Art Blocks Inc. import "../../interfaces/v0.8.x/IMinterBaseV0.sol"; import "../../interfaces/v0.8.x/IGenArt721CoreContractV3_Base.sol"; import "../../interfaces/v0.8.x/IGenArt721CoreContractV3.sol"; import "../../interfaces/v0.8.x/IGenArt721CoreContractV3_Engine.sol"; import "@openzeppelin-4.7/contracts/token/ERC20/IERC20.sol"; pragma solidity ^0.8.0; /** * @title Art Blocks Minter Base Class * @notice A base class for Art Blocks minter contracts that provides common * functionality used across minter contracts. * This contract is not intended to be deployed directly, but rather to be * inherited by other minter contracts. * From a design perspective, this contract is intended to remain simple and * easy to understand. It is not intended to cause a complex inheritance tree, * and instead should keep minter contracts as readable as possible for * collectors and developers. * @dev Semantic versioning is used in the solidity file name, and is therefore * controlled by contracts importing the appropriate filename version. * @author Art Blocks Inc. */ abstract contract MinterBase is IMinterBaseV0 { /// state variable that tracks whether this contract's associated core /// contract is an Engine contract, where Engine contracts have an /// additional revenue split for the platform provider bool public immutable isEngine; // @dev we do not track an initialization state, as the only state variable // is immutable, which the compiler enforces to be assigned during // construction. /** * @notice Initializes contract to ensure state variable `isEngine` is set * appropriately based on the minter's associated core contract address. * @param genArt721Address Art Blocks core contract address for * which this contract will be a minter. */ constructor(address genArt721Address) { // set state variable isEngine isEngine = _getV3CoreIsEngine(genArt721Address); } /** * @notice splits ETH funds between sender (if refund), providers, * artist, and artist's additional payee for a token purchased on * project `_projectId`. * WARNING: This function uses msg.value and msg.sender to determine * refund amounts, and therefore may not be applicable to all use cases * (e.g. do not use with Dutch Auctions with on-chain settlement). * @dev possible DoS during splits is acknowledged, and mitigated by * business practices, including end-to-end testing on mainnet, and * admin-accepted artist payment addresses. * @param projectId Project ID for which funds shall be split. * @param pricePerTokenInWei Current price of token, in Wei. */ function splitFundsETH( uint256 projectId, uint256 pricePerTokenInWei, address genArt721CoreAddress ) internal { if (msg.value > 0) { bool success_; // send refund to sender uint256 refund = msg.value - pricePerTokenInWei; if (refund > 0) { (success_, ) = msg.sender.call{value: refund}(""); require(success_, "Refund failed"); } // split revenues splitRevenuesETH( projectId, pricePerTokenInWei, genArt721CoreAddress ); } } /** * @notice splits ETH revenues between providers, artist, and artist's * additional payee for revenue generated by project `_projectId`. * @dev possible DoS during splits is acknowledged, and mitigated by * business practices, including end-to-end testing on mainnet, and * admin-accepted artist payment addresses. * @param projectId Project ID for which funds shall be split. * @param valueInWei Value to be split, in Wei. */ function splitRevenuesETH( uint256 projectId, uint256 valueInWei, address genArtCoreContract ) internal { if (valueInWei <= 0) { return; // return early } bool success; // split funds between platforms, artist, and artist's // additional payee uint256 renderProviderRevenue_; address payable renderProviderAddress_; uint256 artistRevenue_; address payable artistAddress_; uint256 additionalPayeePrimaryRevenue_; address payable additionalPayeePrimaryAddress_; if (isEngine) { // get engine splits uint256 platformProviderRevenue_; address payable platformProviderAddress_; ( renderProviderRevenue_, renderProviderAddress_, platformProviderRevenue_, platformProviderAddress_, artistRevenue_, artistAddress_, additionalPayeePrimaryRevenue_, additionalPayeePrimaryAddress_ ) = IGenArt721CoreContractV3_Engine(genArtCoreContract) .getPrimaryRevenueSplits(projectId, valueInWei); // Platform Provider payment (only possible if engine) if (platformProviderRevenue_ > 0) { (success, ) = platformProviderAddress_.call{ value: platformProviderRevenue_ }(""); require(success, "Platform Provider payment failed"); } } else { // get flagship splits ( renderProviderRevenue_, // artblocks revenue renderProviderAddress_, // artblocks address artistRevenue_, artistAddress_, additionalPayeePrimaryRevenue_, additionalPayeePrimaryAddress_ ) = IGenArt721CoreContractV3(genArtCoreContract) .getPrimaryRevenueSplits(projectId, valueInWei); } // Render Provider / Art Blocks payment if (renderProviderRevenue_ > 0) { (success, ) = renderProviderAddress_.call{ value: renderProviderRevenue_ }(""); require(success, "Render Provider payment failed"); } // artist payment if (artistRevenue_ > 0) { (success, ) = artistAddress_.call{value: artistRevenue_}(""); require(success, "Artist payment failed"); } // additional payee payment if (additionalPayeePrimaryRevenue_ > 0) { (success, ) = additionalPayeePrimaryAddress_.call{ value: additionalPayeePrimaryRevenue_ }(""); require(success, "Additional Payee payment failed"); } } /** * @notice splits ERC-20 funds between providers, artist, and artist's * additional payee, for a token purchased on project `_projectId`. * @dev possible DoS during splits is acknowledged, and mitigated by * business practices, including end-to-end testing on mainnet, and * admin-accepted artist payment addresses. */ function splitFundsERC20( uint256 projectId, uint256 pricePerTokenInWei, address currencyAddress, address genArtCoreContract ) internal { IERC20 _projectCurrency = IERC20(currencyAddress); // split remaining funds between foundation, artist, and artist's // additional payee uint256 renderProviderRevenue_; address payable renderProviderAddress_; uint256 artistRevenue_; address payable artistAddress_; uint256 additionalPayeePrimaryRevenue_; address payable additionalPayeePrimaryAddress_; if (isEngine) { // get engine splits uint256 platformProviderRevenue_; address payable platformProviderAddress_; ( renderProviderRevenue_, renderProviderAddress_, platformProviderRevenue_, platformProviderAddress_, artistRevenue_, artistAddress_, additionalPayeePrimaryRevenue_, additionalPayeePrimaryAddress_ ) = IGenArt721CoreContractV3_Engine(genArtCoreContract) .getPrimaryRevenueSplits(projectId, pricePerTokenInWei); // Platform Provider payment (only possible if engine) if (platformProviderRevenue_ > 0) { _projectCurrency.transferFrom( msg.sender, platformProviderAddress_, platformProviderRevenue_ ); } } else { // get flagship splits ( renderProviderRevenue_, // artblocks revenue renderProviderAddress_, // artblocks address artistRevenue_, artistAddress_, additionalPayeePrimaryRevenue_, additionalPayeePrimaryAddress_ ) = IGenArt721CoreContractV3(genArtCoreContract) .getPrimaryRevenueSplits(projectId, pricePerTokenInWei); } // Art Blocks payment if (renderProviderRevenue_ > 0) { _projectCurrency.transferFrom( msg.sender, renderProviderAddress_, renderProviderRevenue_ ); } // artist payment if (artistRevenue_ > 0) { _projectCurrency.transferFrom( msg.sender, artistAddress_, artistRevenue_ ); } // additional payee payment if (additionalPayeePrimaryRevenue_ > 0) { _projectCurrency.transferFrom( msg.sender, additionalPayeePrimaryAddress_, additionalPayeePrimaryRevenue_ ); } } /** * @notice Returns whether a V3 core contract is an Art Blocks Engine * contract or not. Return value of false indicates that the core is a * flagship contract. * @dev this function reverts if a core contract does not return the * expected number of return values from getPrimaryRevenueSplits() for * either a flagship or engine core contract. * @dev this function uses the length of the return data (in bytes) to * determine whether the core is an engine or not. * @param genArt721CoreV3 The address of the deployed core contract. */ function _getV3CoreIsEngine( address genArt721CoreV3 ) private returns (bool) { // call getPrimaryRevenueSplits() on core contract bytes memory payload = abi.encodeWithSignature( "getPrimaryRevenueSplits(uint256,uint256)", 0, 0 ); (bool success, bytes memory returnData) = genArt721CoreV3.call(payload); require(success, "getPrimaryRevenueSplits() call failed"); // determine whether core is engine or not, based on return data length uint256 returnDataLength = returnData.length; if (returnDataLength == 6 * 32) { // 6 32-byte words returned if flagship (not engine) // @dev 6 32-byte words are expected because the non-engine core // contracts return a payout address and uint256 payment value for // the artist, and artist's additional payee, and Art Blocks. // also note that per Solidity ABI encoding, the address return // values are padded to 32 bytes. return false; } else if (returnDataLength == 8 * 32) { // 8 32-byte words returned if engine // @dev 8 32-byte words are expected because the engine core // contracts return a payout address and uint256 payment value for // the artist, artist's additional payee, render provider // typically Art Blocks, and platform provider (partner). // also note that per Solidity ABI encoding, the address return // values are padded to 32 bytes. return true; } else { // unexpected return value length revert("Unexpected revenue split bytes"); } } } // SPDX-License-Identifier: LGPL-3.0-only // Created By: Art Blocks Inc. import "../../interfaces/v0.8.x/IGenArt721CoreContractV3_Base.sol"; import "../../interfaces/v0.8.x/IMinterFilterV0.sol"; import "../../interfaces/v0.8.x/IFilteredMinterV2.sol"; import "./MinterBase_v0_1_1.sol"; import "@openzeppelin-4.5/contracts/security/ReentrancyGuard.sol"; pragma solidity 0.8.19; /** * @title Filtered Minter contract that allows tokens to be minted with ETH. * This is designed to be used with GenArt721CoreContractV3 flagship or * engine contracts. * @author Art Blocks Inc. * @notice Privileged Roles and Ownership: * This contract is designed to be managed, with limited powers. * Privileged roles and abilities are controlled by the project's artist, which * can be modified by the core contract's Admin ACL contract. Both of these * roles hold extensive power and can modify minter details. * Care must be taken to ensure that the admin ACL contract and artist * addresses are secure behind a multi-sig or other access control mechanism. * ---------------------------------------------------------------------------- * The following functions are restricted to a project's artist: * - updatePricePerTokenInWei * - setProjectMaxInvocations * - manuallyLimitProjectMaxInvocations * ---------------------------------------------------------------------------- * Additional admin and artist privileged roles may be described on other * contracts that this minter integrates with. */ contract MinterSetPriceV4 is ReentrancyGuard, MinterBase, IFilteredMinterV2 { /// Core contract address this minter interacts with address public immutable genArt721CoreAddress; /// The core contract integrates with V3 contracts IGenArt721CoreContractV3_Base private immutable genArtCoreContract_Base; /// Minter filter address this minter interacts with address public immutable minterFilterAddress; /// Minter filter this minter may interact with. IMinterFilterV0 private immutable minterFilter; /// minterType for this minter string public constant minterType = "MinterSetPriceV4"; /// minter version for this minter string public constant minterVersion = "v4.1.0"; uint256 constant ONE_MILLION = 1_000_000; struct ProjectConfig { bool maxHasBeenInvoked; bool priceIsConfigured; uint24 maxInvocations; uint256 pricePerTokenInWei; } mapping(uint256 => ProjectConfig) public projectConfig; function _onlyArtist(uint256 _projectId) internal view { require( msg.sender == genArtCoreContract_Base.projectIdToArtistAddress(_projectId), "Only Artist" ); } /** * @notice Initializes contract to be a Filtered Minter for * `_minterFilter`, integrated with Art Blocks core contract * at address `_genArt721Address`. * @param _genArt721Address Art Blocks core contract address for * which this contract will be a minter. * @param _minterFilter Minter filter for which this will be a * filtered minter. */ constructor( address _genArt721Address, address _minterFilter ) ReentrancyGuard() MinterBase(_genArt721Address) { genArt721CoreAddress = _genArt721Address; // always populate immutable engine contracts, but only use appropriate // interface based on isEngine in the rest of the contract genArtCoreContract_Base = IGenArt721CoreContractV3_Base( _genArt721Address ); minterFilterAddress = _minterFilter; minterFilter = IMinterFilterV0(_minterFilter); require( minterFilter.genArt721CoreAddress() == _genArt721Address, "Illegal contract pairing" ); } /** * @notice Syncs local maximum invocations of project `_projectId` based on * the value currently defined in the core contract. * @param _projectId Project ID to set the maximum invocations for. * @dev this enables gas reduction after maxInvocations have been reached - * core contracts shall still enforce a maxInvocation check during mint. */ function setProjectMaxInvocations(uint256 _projectId) public { _onlyArtist(_projectId); uint256 maxInvocations; uint256 invocations; (invocations, maxInvocations, , , , ) = genArtCoreContract_Base .projectStateData(_projectId); // update storage with results projectConfig[_projectId].maxInvocations = uint24(maxInvocations); // We need to ensure maxHasBeenInvoked is correctly set after manually syncing the // local maxInvocations value with the core contract's maxInvocations value. // This synced value of maxInvocations from the core contract will always be greater // than or equal to the previous value of maxInvocations stored locally. projectConfig[_projectId].maxHasBeenInvoked = invocations == maxInvocations; emit ProjectMaxInvocationsLimitUpdated(_projectId, maxInvocations); } /** * @notice Manually sets the local maximum invocations of project `_projectId` * with the provided `_maxInvocations`, checking that `_maxInvocations` is less * than or equal to the value of project `_project_id`'s maximum invocations that is * set on the core contract. * @dev Note that a `_maxInvocations` of 0 can only be set if the current `invocations` * value is also 0 and this would also set `maxHasBeenInvoked` to true, correctly short-circuiting * this minter's purchase function, avoiding extra gas costs from the core contract's maxInvocations check. * @param _projectId Project ID to set the maximum invocations for. * @param _maxInvocations Maximum invocations to set for the project. */ function manuallyLimitProjectMaxInvocations( uint256 _projectId, uint256 _maxInvocations ) external { _onlyArtist(_projectId); // CHECKS // ensure that the manually set maxInvocations is not greater than what is set on the core contract uint256 maxInvocations; uint256 invocations; (invocations, maxInvocations, , , , ) = genArtCoreContract_Base .projectStateData(_projectId); require( _maxInvocations <= maxInvocations, "Cannot increase project max invocations above core contract set project max invocations" ); require( _maxInvocations >= invocations, "Cannot set project max invocations to less than current invocations" ); // EFFECTS // update storage with results projectConfig[_projectId].maxInvocations = uint24(_maxInvocations); // We need to ensure maxHasBeenInvoked is correctly set after manually setting the // local maxInvocations value. projectConfig[_projectId].maxHasBeenInvoked = invocations == _maxInvocations; emit ProjectMaxInvocationsLimitUpdated(_projectId, _maxInvocations); } /** * @notice Warning: Disabling purchaseTo is not supported on this minter. * This method exists purely for interface-conformance purposes. */ function togglePurchaseToDisabled(uint256 _projectId) external view { _onlyArtist(_projectId); revert("Action not supported"); } /** * @notice projectId => has project reached its maximum number of * invocations? Note that this returns a local cache of the core contract's * state, and may be out of sync with the core contract. This is * intentional, as it only enables gas optimization of mints after a * project's maximum invocations has been reached. A false negative will * only result in a gas cost increase, since the core contract will still * enforce a maxInvocation check during minting. A false positive is not * possible because the V3 core contract only allows maximum invocations * to be reduced, not increased. Based on this rationale, we intentionally * do not do input validation in this method as to whether or not the input * `_projectId` is an existing project ID. */ function projectMaxHasBeenInvoked( uint256 _projectId ) external view returns (bool) { return projectConfig[_projectId].maxHasBeenInvoked; } /** * @notice projectId => project's maximum number of invocations. * Optionally synced with core contract value, for gas optimization. * Note that this returns a local cache of the core contract's * state, and may be out of sync with the core contract. This is * intentional, as it only enables gas optimization of mints after a * project's maximum invocations has been reached. * @dev A number greater than the core contract's project max invocations * will only result in a gas cost increase, since the core contract will * still enforce a maxInvocation check during minting. A number less than * the core contract's project max invocations is only possible when the * project's max invocations have not been synced on this minter, since the * V3 core contract only allows maximum invocations to be reduced, not * increased. When this happens, the minter will enable minting, allowing * the core contract to enforce the max invocations check. Based on this * rationale, we intentionally do not do input validation in this method as * to whether or not the input `_projectId` is an existing project ID. */ function projectMaxInvocations( uint256 _projectId ) external view returns (uint256) { return uint256(projectConfig[_projectId].maxInvocations); } /** * @notice Updates this minter's price per token of project `_projectId` * to be '_pricePerTokenInWei`, in Wei. * This price supersedes any legacy core contract price per token value. * @dev Note that it is intentionally supported here that the configured * price may be explicitly set to `0`. */ function updatePricePerTokenInWei( uint256 _projectId, uint256 _pricePerTokenInWei ) external { _onlyArtist(_projectId); ProjectConfig storage _projectConfig = projectConfig[_projectId]; _projectConfig.pricePerTokenInWei = _pricePerTokenInWei; _projectConfig.priceIsConfigured = true; emit PricePerTokenInWeiUpdated(_projectId, _pricePerTokenInWei); // sync local max invocations if not initially populated // @dev if local max invocations and maxHasBeenInvoked are both // initial values, we know they have not been populated. if ( _projectConfig.maxInvocations == 0 && _projectConfig.maxHasBeenInvoked == false ) { setProjectMaxInvocations(_projectId); } } /** * @notice Purchases a token from project `_projectId`. * @param _projectId Project ID to mint a token on. * @return tokenId Token ID of minted token */ function purchase( uint256 _projectId ) external payable returns (uint256 tokenId) { tokenId = purchaseTo_do6(msg.sender, _projectId); return tokenId; } /** * @notice gas-optimized version of purchase(uint256). */ function purchase_H4M( uint256 _projectId ) external payable returns (uint256 tokenId) { tokenId = purchaseTo_do6(msg.sender, _projectId); return tokenId; } /** * @notice Purchases a token from project `_projectId` and sets * the token's owner to `_to`. * @param _to Address to be the new token's owner. * @param _projectId Project ID to mint a token on. * @return tokenId Token ID of minted token */ function purchaseTo( address _to, uint256 _projectId ) external payable returns (uint256 tokenId) { return purchaseTo_do6(_to, _projectId); } /** * @notice gas-optimized version of purchaseTo(address, uint256). */ function purchaseTo_do6( address _to, uint256 _projectId ) public payable nonReentrant returns (uint256 tokenId) { // CHECKS ProjectConfig storage _projectConfig = projectConfig[_projectId]; // Note that `maxHasBeenInvoked` is only checked here to reduce gas // consumption after a project has been fully minted. // `_projectConfig.maxHasBeenInvoked` is locally cached to reduce // gas consumption, but if not in sync with the core contract's value, // the core contract also enforces its own max invocation check during // minting. require( !_projectConfig.maxHasBeenInvoked, "Maximum number of invocations reached" ); // require artist to have configured price of token on this minter require(_projectConfig.priceIsConfigured, "Price not configured"); // load price of token into memory uint256 pricePerTokenInWei = _projectConfig.pricePerTokenInWei; require( msg.value >= pricePerTokenInWei, "Must send minimum value to mint!" ); // EFFECTS tokenId = minterFilter.mint(_to, _projectId, msg.sender); // invocation is token number plus one, and will never overflow due to // limit of 1e6 invocations per project. block scope for gas efficiency // (i.e. avoid an unnecessary var initialization to 0). unchecked { uint256 tokenInvocation = (tokenId % ONE_MILLION) + 1; uint256 localMaxInvocations = _projectConfig.maxInvocations; // handle the case where the token invocation == minter local max // invocations occurred on a different minter, and we have a stale // local maxHasBeenInvoked value returning a false negative. // @dev this is a CHECK after EFFECTS, so security was considered // in detail here. require( tokenInvocation <= localMaxInvocations, "Maximum invocations reached" ); // in typical case, update the local maxHasBeenInvoked value // to true if the token invocation == minter local max invocations // (enables gas efficient reverts after sellout) if (tokenInvocation == localMaxInvocations) { _projectConfig.maxHasBeenInvoked = true; } } // INTERACTIONS splitFundsETH(_projectId, pricePerTokenInWei, genArt721CoreAddress); return tokenId; } /** * @notice Gets if price of token is configured, price of minting a * token on project `_projectId`, and currency symbol and address to be * used as payment. Supersedes any core contract price information. * @param _projectId Project ID to get price information for. * @return isConfigured true only if token price has been configured on * this minter * @return tokenPriceInWei current price of token on this minter - invalid * if price has not yet been configured * @return currencySymbol currency symbol for purchases of project on this * minter. This minter always returns "ETH" * @return currencyAddress currency address for purchases of project on * this minter. This minter always returns null address, reserved for ether */ function getPriceInfo( uint256 _projectId ) external view returns ( bool isConfigured, uint256 tokenPriceInWei, string memory currencySymbol, address currencyAddress ) { ProjectConfig storage _projectConfig = projectConfig[_projectId]; isConfigured = _projectConfig.priceIsConfigured; tokenPriceInWei = _projectConfig.pricePerTokenInWei; currencySymbol = "ETH"; currencyAddress = address(0); } }
File 2 of 5: GenArt721CoreV3_Engine_Flex
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol) pragma solidity ^0.8.0; import "../utils/Context.sol"; /** * @dev Contract module which provides a basic access control mechanism, where * there is an account (an owner) that can be granted exclusive access to * specific functions. * * By default, the owner account will be the one that deploys the contract. This * can later be changed with {transferOwnership}. * * This module is used through inheritance. It will make available the modifier * `onlyOwner`, which can be applied to your functions to restrict their use to * the owner. */ abstract contract Ownable is Context { address private _owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev Initializes the contract setting the deployer as the initial owner. */ constructor() { _transferOwnership(_msgSender()); } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { _checkOwner(); _; } /** * @dev Returns the address of the current owner. */ function owner() public view virtual returns (address) { return _owner; } /** * @dev Throws if the sender is not the owner. */ function _checkOwner() internal view virtual { require(owner() == _msgSender(), "Ownable: caller is not the owner"); } /** * @dev Leaves the contract without owner. It will not be possible to call * `onlyOwner` functions anymore. Can only be called by the current owner. * * NOTE: Renouncing ownership will leave the contract without an owner, * thereby removing any functionality that is only available to the owner. */ function renounceOwnership() public virtual onlyOwner { _transferOwnership(address(0)); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Can only be called by the current owner. */ function transferOwnership(address newOwner) public virtual onlyOwner { require(newOwner != address(0), "Ownable: new owner is the zero address"); _transferOwnership(newOwner); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Internal function without access restriction. */ function _transferOwnership(address newOwner) internal virtual { address oldOwner = _owner; _owner = newOwner; emit OwnershipTransferred(oldOwner, newOwner); } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol) pragma solidity ^0.8.0; import "../IERC721.sol"; /** * @title ERC-721 Non-Fungible Token Standard, optional metadata extension * @dev See https://eips.ethereum.org/EIPS/eip-721 */ interface IERC721Metadata is IERC721 { /** * @dev Returns the token collection name. */ function name() external view returns (string memory); /** * @dev Returns the token collection symbol. */ function symbol() external view returns (string memory); /** * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token. */ function tokenURI(uint256 tokenId) external view returns (string memory); } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/IERC721.sol) pragma solidity ^0.8.0; import "../../utils/introspection/IERC165.sol"; /** * @dev Required interface of an ERC721 compliant contract. */ interface IERC721 is IERC165 { /** * @dev Emitted when `tokenId` token is transferred from `from` to `to`. */ event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); /** * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token. */ event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); /** * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. */ event ApprovalForAll(address indexed owner, address indexed operator, bool approved); /** * @dev Returns the number of tokens in ``owner``'s account. */ function balanceOf(address owner) external view returns (uint256 balance); /** * @dev Returns the owner of the `tokenId` token. * * Requirements: * * - `tokenId` must exist. */ function ownerOf(uint256 tokenId) external view returns (address owner); /** * @dev Safely transfers `tokenId` token from `from` to `to`. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must exist and be owned by `from`. * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. * * Emits a {Transfer} event. */ function safeTransferFrom( address from, address to, uint256 tokenId, bytes calldata data ) external; /** * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients * are aware of the ERC721 protocol to prevent tokens from being forever locked. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must exist and be owned by `from`. * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}. * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. * * Emits a {Transfer} event. */ function safeTransferFrom( address from, address to, uint256 tokenId ) external; /** * @dev Transfers `tokenId` token from `from` to `to`. * * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must be owned by `from`. * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. * * Emits a {Transfer} event. */ function transferFrom( address from, address to, uint256 tokenId ) external; /** * @dev Gives permission to `to` to transfer `tokenId` token to another account. * The approval is cleared when the token is transferred. * * Only a single account can be approved at a time, so approving the zero address clears previous approvals. * * Requirements: * * - The caller must own the token or be an approved operator. * - `tokenId` must exist. * * Emits an {Approval} event. */ function approve(address to, uint256 tokenId) external; /** * @dev Approve or remove `operator` as an operator for the caller. * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller. * * Requirements: * * - The `operator` cannot be the caller. * * Emits an {ApprovalForAll} event. */ function setApprovalForAll(address operator, bool _approved) external; /** * @dev Returns the account approved for `tokenId` token. * * Requirements: * * - `tokenId` must exist. */ function getApproved(uint256 tokenId) external view returns (address operator); /** * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`. * * See {setApprovalForAll} */ function isApprovedForAll(address owner, address operator) external view returns (bool); } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol) pragma solidity ^0.8.0; /** * @title ERC721 token receiver interface * @dev Interface for any contract that wants to support safeTransfers * from ERC721 asset contracts. */ interface IERC721Receiver { /** * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom} * by `operator` from `from`, this function is called. * * It must return its Solidity selector to confirm the token transfer. * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted. * * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`. */ function onERC721Received( address operator, address from, uint256 tokenId, bytes calldata data ) external returns (bytes4); } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol) pragma solidity ^0.8.1; /** * @dev Collection of functions related to the address type */ library Address { /** * @dev Returns true if `account` is a contract. * * [IMPORTANT] * ==== * It is unsafe to assume that an address for which this function returns * false is an externally-owned account (EOA) and not a contract. * * Among others, `isContract` will return false for the following * types of addresses: * * - an externally-owned account * - a contract in construction * - an address where a contract will be created * - an address where a contract lived, but was destroyed * ==== * * [IMPORTANT] * ==== * You shouldn't rely on `isContract` to protect against flash loan attacks! * * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract * constructor. * ==== */ function isContract(address account) internal view returns (bool) { // This method relies on extcodesize/address.code.length, which returns 0 // for contracts in construction, since the code is only stored at the end // of the constructor execution. return account.code.length > 0; } /** * @dev Replacement for Solidity's `transfer`: sends `amount` wei to * `recipient`, forwarding all available gas and reverting on errors. * * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost * of certain opcodes, possibly making contracts go over the 2300 gas limit * imposed by `transfer`, making them unable to receive funds via * `transfer`. {sendValue} removes this limitation. * * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. * * IMPORTANT: because control is transferred to `recipient`, care must be * taken to not create reentrancy vulnerabilities. Consider using * {ReentrancyGuard} or the * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. */ function sendValue(address payable recipient, uint256 amount) internal { require(address(this).balance >= amount, "Address: insufficient balance"); (bool success, ) = recipient.call{value: amount}(""); require(success, "Address: unable to send value, recipient may have reverted"); } /** * @dev Performs a Solidity function call using a low level `call`. A * plain `call` is an unsafe replacement for a function call: use this * function instead. * * If `target` reverts with a revert reason, it is bubbled up by this * function (like regular Solidity function calls). * * Returns the raw returned data. To convert to the expected return value, * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. * * Requirements: * * - `target` must be a contract. * - calling `target` with `data` must not revert. * * _Available since v3.1._ */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { return functionCall(target, data, "Address: low-level call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with * `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { return functionCallWithValue(target, data, 0, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but also transferring `value` wei to `target`. * * Requirements: * * - the calling contract must have an ETH balance of at least `value`. * - the called Solidity function must be `payable`. * * _Available since v3.1._ */ function functionCallWithValue( address target, bytes memory data, uint256 value ) internal returns (bytes memory) { return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); } /** * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but * with `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCallWithValue( address target, bytes memory data, uint256 value, string memory errorMessage ) internal returns (bytes memory) { require(address(this).balance >= value, "Address: insufficient balance for call"); require(isContract(target), "Address: call to non-contract"); (bool success, bytes memory returndata) = target.call{value: value}(data); return verifyCallResult(success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { return functionStaticCall(target, data, "Address: low-level static call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall( address target, bytes memory data, string memory errorMessage ) internal view returns (bytes memory) { require(isContract(target), "Address: static call to non-contract"); (bool success, bytes memory returndata) = target.staticcall(data); return verifyCallResult(success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { return functionDelegateCall(target, data, "Address: low-level delegate call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { require(isContract(target), "Address: delegate call to non-contract"); (bool success, bytes memory returndata) = target.delegatecall(data); return verifyCallResult(success, returndata, errorMessage); } /** * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the * revert reason using the provided one. * * _Available since v4.3._ */ function verifyCallResult( bool success, bytes memory returndata, string memory errorMessage ) internal pure returns (bytes memory) { if (success) { return returndata; } else { // Look for revert reason and bubble it up if present if (returndata.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly /// @solidity memory-safe-assembly assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert(errorMessage); } } } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/Context.sol) pragma solidity ^0.8.0; /** * @dev Provides information about the current execution context, including the * sender of the transaction and its data. While these are generally available * via msg.sender and msg.data, they should not be accessed in such a direct * manner, since when dealing with meta-transactions the account sending and * paying for execution may not be the actual sender (as far as an application * is concerned). * * This contract is only required for intermediate, library-like contracts. */ abstract contract Context { function _msgSender() internal view virtual returns (address) { return msg.sender; } function _msgData() internal view virtual returns (bytes calldata) { return msg.data; } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol) pragma solidity ^0.8.0; import "./IERC165.sol"; /** * @dev Implementation of the {IERC165} interface. * * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check * for the additional interface id that will be supported. For example: * * ```solidity * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); * } * ``` * * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation. */ abstract contract ERC165 is IERC165 { /** * @dev See {IERC165-supportsInterface}. */ function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { return interfaceId == type(IERC165).interfaceId; } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) pragma solidity ^0.8.0; /** * @dev Interface of the ERC165 standard, as defined in the * https://eips.ethereum.org/EIPS/eip-165[EIP]. * * Implementers can declare support of contract interfaces, which can then be * queried by others ({ERC165Checker}). * * For an implementation, see {ERC165}. */ interface IERC165 { /** * @dev Returns true if this contract implements the interface defined by * `interfaceId`. See the corresponding * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] * to learn more about how these ids are created. * * This function call must use less than 30 000 gas. */ function supportsInterface(bytes4 interfaceId) external view returns (bool); } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol) pragma solidity ^0.8.0; /** * @dev String operations. */ 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); } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity 0.8.19; // Created By: Art Blocks Inc. import "../../interfaces/v0.8.x/IRandomizer_V3CoreBase.sol"; import "../../interfaces/v0.8.x/IAdminACLV0.sol"; import "../../interfaces/v0.8.x/IGenArt721CoreContractV3_Engine_Flex.sol"; import "../../interfaces/v0.8.x/IGenArt721CoreContractExposesHashSeed.sol"; import "../../interfaces/v0.8.x/IDependencyRegistryCompatibleV0.sol"; import "../../interfaces/v0.8.x/IManifold.sol"; import "@openzeppelin-4.7/contracts/access/Ownable.sol"; import "../../libs/v0.8.x/ERC721_PackedHashSeed.sol"; import "../../libs/v0.8.x/BytecodeStorageV1.sol"; import "../../libs/v0.8.x/Bytes32Strings.sol"; /** * @title Art Blocks Engine Flex ERC-721 core contract, V3. * @author Art Blocks Inc. * @notice Privileged Roles and Ownership: * This contract is designed to be managed, with progressively limited powers * as a project progresses from active to locked. * Privileged roles and abilities are controlled by the admin ACL contract and * artists. Both of these roles hold extensive power and can arbitrarily * control and modify portions of projects, dependent upon project state. After * a project is locked, important project metadata fields are locked including * the project name, artist name, and script and display details. Edition size * can never be increased. * Care must be taken to ensure that the admin ACL contract and artist * addresses are secure behind a multi-sig or other access control mechanism. * ---------------------------------------------------------------------------- * The following functions are restricted to the Admin ACL contract: * - updateArtblocksDependencyRegistryAddress * - updateProviderSalesAddresses * - updateProviderPrimarySalesPercentages (up to 100%) * - updateProviderSecondarySalesBPS (up to 100%) * - updateMinterContract * - updateRandomizerAddress * - toggleProjectIsActive * - addProject * - forbidNewProjects (forever forbidding new projects) * - updateDefaultBaseURI (used to initialize new project base URIs) * - updateIPFSGateway * - updateArweaveGateway * ---------------------------------------------------------------------------- * The following functions are restricted to either the the Artist address or * the Admin ACL contract, only when the project is not locked: * - updateProjectName * - updateProjectArtistName * - updateProjectLicense * - Change project script via addProjectScript, updateProjectScript, * and removeProjectLastScript * - updateProjectScriptType * - updateProjectAspectRatio * ---------------------------------------------------------------------------- * The following functions are restricted to only the Artist address: * - proposeArtistPaymentAddressesAndSplits (Note that this has to be accepted * by adminAcceptArtistAddressesAndSplits to take effect, which is restricted * to the Admin ACL contract, or the artist if the core contract owner has * renounced ownership. Also note that a proposal will be automatically * accepted if the artist only proposes changed payee percentages without * modifying any payee addresses, or is only removing payee addresses, or * if the global config `autoApproveArtistSplitProposals` is set to `true`.) * - toggleProjectIsPaused (note the artist can still mint while paused) * - updateProjectSecondaryMarketRoyaltyPercentage (up to ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE percent) * - updateProjectWebsite * - updateProjectMaxInvocations (to a number greater than or equal to the * current number of invocations, and less than current project maximum * invocations) * - updateProjectBaseURI (controlling the base URI for tokens in the project) * ---------------------------------------------------------------------------- * The following function is restricted to either the Admin ACL contract, or * the Artist address if the core contract owner has renounced ownership: * - adminAcceptArtistAddressesAndSplits * - updateProjectArtistAddress (owner ultimately controlling the project and * its and-on revenue, unless owner has renounced ownership) * ---------------------------------------------------------------------------- * The following function is restricted to the artist when a project is * unlocked, and only callable by Admin ACL contract when a project is locked: * - updateProjectDescription * ---------------------------------------------------------------------------- * The following functions for managing external asset dependencies are restricted * to projects with external asset dependencies that are unlocked: * - lockProjectExternalAssetDependencies * - updateProjectExternalAssetDependency * - removeProjectExternalAssetDependency * - addProjectExternalAssetDependency * ---------------------------------------------------------------------------- * The following function is restricted to owner calling directly: * - transferOwnership * - renounceOwnership * ---------------------------------------------------------------------------- * The following configuration variables are set at time of contract deployment, * and not modifiable thereafter (immutable after the point of deployment): * - (bool) autoApproveArtistSplitProposals * ---------------------------------------------------------------------------- * Additional admin and artist privileged roles may be described on minters, * registries, and other contracts that may interact with this core contract. */ contract GenArt721CoreV3_Engine_Flex is ERC721_PackedHashSeed, Ownable, IDependencyRegistryCompatibleV0, IManifold, IGenArt721CoreContractV3_Engine_Flex, IGenArt721CoreContractExposesHashSeed { using BytecodeStorageWriter for string; using Bytes32Strings for bytes32; uint256 constant ONE_HUNDRED = 100; uint256 constant ONE_MILLION = 1_000_000; uint24 constant ONE_MILLION_UINT24 = 1_000_000; uint256 constant FOUR_WEEKS_IN_SECONDS = 2_419_200; uint8 constant AT_CHARACTER_CODE = uint8(bytes1("@")); // 0x40 // numeric constants uint256 constant MAX_PROVIDER_SECONDARY_SALES_BPS = 10000; // 10_000 BPS = 100% uint256 constant ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE = 95; // 95% // This contract emits generic events that contain fields that indicate // which parameter has been updated. This is sufficient for application // state management, while also simplifying the contract and indexing code. // This was done as an alternative to having custom events that emit what // field-values have changed for each event, given that changed values can // be introspected by indexers due to the design of this smart contract // exposing these state changes via publicly viewable fields. // // The following fields are used to indicate which contract-level parameter // has been updated in the `PlatformUpdated` event: bytes32 constant FIELD_NEXT_PROJECT_ID = "nextProjectId"; bytes32 constant FIELD_NEW_PROJECTS_FORBIDDEN = "newProjectsForbidden"; bytes32 constant FIELD_DEFAULT_BASE_URI = "defaultBaseURI"; bytes32 constant FIELD_RANDOMIZER_ADDRESS = "randomizerAddress"; bytes32 constant FIELD_ARTBLOCKS_DEPENDENCY_REGISTRY_ADDRESS = "dependencyRegistryAddress"; bytes32 constant FIELD_PROVIDER_SALES_ADDRESSES = "providerSalesAddresses"; bytes32 constant FIELD_PROVIDER_PRIMARY_SALES_PERCENTAGES = "providerPrimaryPercentages"; bytes32 constant FIELD_PROVIDER_SECONDARY_SALES_BPS = "providerSecondaryBPS"; // The following fields are used to indicate which project-level parameter // has been updated in the `ProjectUpdated` event: bytes32 constant FIELD_PROJECT_COMPLETED = "completed"; bytes32 constant FIELD_PROJECT_ACTIVE = "active"; bytes32 constant FIELD_PROJECT_ARTIST_ADDRESS = "artistAddress"; bytes32 constant FIELD_PROJECT_PAUSED = "paused"; bytes32 constant FIELD_PROJECT_CREATED = "created"; bytes32 constant FIELD_PROJECT_NAME = "name"; bytes32 constant FIELD_PROJECT_ARTIST_NAME = "artistName"; bytes32 constant FIELD_PROJECT_SECONDARY_MARKET_ROYALTY_PERCENTAGE = "royaltyPercentage"; bytes32 constant FIELD_PROJECT_DESCRIPTION = "description"; bytes32 constant FIELD_PROJECT_WEBSITE = "website"; bytes32 constant FIELD_PROJECT_LICENSE = "license"; bytes32 constant FIELD_PROJECT_MAX_INVOCATIONS = "maxInvocations"; bytes32 constant FIELD_PROJECT_SCRIPT = "script"; bytes32 constant FIELD_PROJECT_SCRIPT_TYPE = "scriptType"; bytes32 constant FIELD_PROJECT_ASPECT_RATIO = "aspectRatio"; bytes32 constant FIELD_PROJECT_BASE_URI = "baseURI"; /// Dependency registry managed by Art Blocks address public artblocksDependencyRegistryAddress; /// current randomizer contract IRandomizer_V3CoreBase public randomizerContract; /// append-only array of all randomizer contract addresses ever used by /// this contract address[] private _historicalRandomizerAddresses; /// admin ACL contract IAdminACLV0 public adminACLContract; struct Project { uint24 invocations; uint24 maxInvocations; uint24 scriptCount; // max uint64 ~= 1.8e19 sec ~= 570 billion years uint64 completedTimestamp; bool active; bool paused; string name; string artist; address descriptionAddress; string website; string license; string projectBaseURI; bytes32 scriptTypeAndVersion; string aspectRatio; // mapping from script index to address storing script in bytecode mapping(uint256 => address) scriptBytecodeAddresses; bool externalAssetDependenciesLocked; uint24 externalAssetDependencyCount; mapping(uint256 => ExternalAssetDependency) externalAssetDependencies; } mapping(uint256 => Project) projects; string public preferredIPFSGateway; string public preferredArweaveGateway; /// packed struct containing project financial information struct ProjectFinance { address payable additionalPayeePrimarySales; // packed uint: max of 95, max uint8 = 255 uint8 secondaryMarketRoyaltyPercentage; address payable additionalPayeeSecondarySales; // packed uint: max of 100, max uint8 = 255 uint8 additionalPayeeSecondarySalesPercentage; address payable artistAddress; // packed uint: max of 100, max uint8 = 255 uint8 additionalPayeePrimarySalesPercentage; } // Project financials mapping mapping(uint256 => ProjectFinance) projectIdToFinancials; /// hash of artist's proposed payment updates to be approved by admin mapping(uint256 => bytes32) public proposedArtistAddressesAndSplitsHash; /// The render provider payment address for all primary sales revenues /// (packed) address payable public renderProviderPrimarySalesAddress; /// Percentage of primary sales revenue allocated to the render provider /// (packed) // packed uint: max of 100, max uint8 = 255 uint8 private _renderProviderPrimarySalesPercentage = 10; /// The platform provider payment address for all primary sales revenues /// (packed) address payable public platformProviderPrimarySalesAddress; /// Percentage of primary sales revenue allocated to the platform provider /// (packed) // packed uint: max of 100, max uint8 = 255 uint8 private _platformProviderPrimarySalesPercentage = 10; /// The render provider payment address for all secondary sales royalty /// revenues address payable public renderProviderSecondarySalesAddress; /// Basis Points of secondary sales royalties allocated to the /// render provider uint256 public renderProviderSecondarySalesBPS = 250; /// The platform provider payment address for all secondary sales royalty /// revenues address payable public platformProviderSecondarySalesAddress; /// Basis Points of secondary sales royalties allocated to the /// platform provider uint256 public platformProviderSecondarySalesBPS = 250; /// single minter allowed for this core contract address public minterContract; /// starting (initial) project ID on this contract uint256 public immutable startingProjectId; /// next project ID to be created uint248 private _nextProjectId; /// bool indicating if adding new projects is forbidden; /// default behavior is to allow new projects bool public newProjectsForbidden; /// configuration variable (determined at time of deployment) /// that determines whether or not admin approval^ should be required /// to accept artist address change proposals, or if these proposals /// should always auto-approve, as determined by the business process /// requirements of the Engine partner using this contract. /// /// ^does not apply in the case where contract-ownership itself is revoked bool public immutable autoApproveArtistSplitProposals; /// version & type of this core contract bytes32 constant CORE_VERSION = "v3.1.4"; function coreVersion() external pure returns (string memory) { return CORE_VERSION.toString(); } bytes32 constant CORE_TYPE = "GenArt721CoreV3_Engine_Flex"; function coreType() external pure returns (string memory) { return CORE_TYPE.toString(); } /// default base URI to initialize all new project projectBaseURI values to string public defaultBaseURI; function _onlyUnlockedProjectExternalAssetDependencies( uint256 _projectId ) internal view { require( !projects[_projectId].externalAssetDependenciesLocked, "External dependencies locked" ); } function _onlyNonZeroAddress(address _address) internal pure { require(_address != address(0), "Must input non-zero address"); } function _onlyNonEmptyString(string memory _string) internal pure { require(bytes(_string).length != 0, "Must input non-empty string"); } function _onlyValidTokenId(uint256 _tokenId) internal view { require(_exists(_tokenId), "Token ID does not exist"); } function _onlyValidProjectId(uint256 _projectId) internal view { require( (_projectId >= startingProjectId) && (_projectId < _nextProjectId), "Project ID does not exist" ); } function _onlyUnlocked(uint256 _projectId) internal view { // Note: calling `_projectUnlocked` enforces that the `_projectId` // passed in is valid.` require(_projectUnlocked(_projectId), "Only if unlocked"); } function _onlyAdminACL(bytes4 _selector) internal { require( adminACLAllowed(msg.sender, address(this), _selector), "Only Admin ACL allowed" ); } function _onlyArtist(uint256 _projectId) internal view { require( msg.sender == projectIdToFinancials[_projectId].artistAddress, "Only artist" ); } function _onlyArtistOrAdminACL( uint256 _projectId, bytes4 _selector ) internal { require( msg.sender == projectIdToFinancials[_projectId].artistAddress || adminACLAllowed(msg.sender, address(this), _selector), "Only artist or Admin ACL allowed" ); } /** * This modifier allows the artist of a project to call a function if the * owner of the contract has renounced ownership. This is to allow the * contract to continue to function if the owner decides to renounce * ownership. */ function _onlyAdminACLOrRenouncedArtist( uint256 _projectId, bytes4 _selector ) internal { require( adminACLAllowed(msg.sender, address(this), _selector) || (owner() == address(0) && msg.sender == projectIdToFinancials[_projectId].artistAddress), "Only Admin ACL allowed, or artist if owner has renounced" ); } /** * @notice Initializes contract. * @param _tokenName Name of token. * @param _tokenSymbol Token symbol. * @param _randomizerContract Randomizer contract. * @param _adminACLContract Address of admin access control contract, to be * set as contract owner. * @param _startingProjectId The initial next project ID. * @param _autoApproveArtistSplitProposals Whether or not to always * auto-approve proposed artist split updates. * @dev _startingProjectId should be set to a value much, much less than * max(uint248), but an explicit input type of `uint248` is used as it is * safer to cast up to `uint256` than it is to cast down for the purposes * of setting `_nextProjectId`. */ constructor( string memory _tokenName, string memory _tokenSymbol, address _renderProviderAddress, address _platformProviderAddress, address _randomizerContract, address _adminACLContract, uint248 _startingProjectId, bool _autoApproveArtistSplitProposals ) ERC721_PackedHashSeed(_tokenName, _tokenSymbol) { _onlyNonZeroAddress(_renderProviderAddress); _onlyNonZeroAddress(_platformProviderAddress); _onlyNonZeroAddress(_randomizerContract); _onlyNonZeroAddress(_adminACLContract); // setup immutable `autoApproveArtistSplitProposals` config autoApproveArtistSplitProposals = _autoApproveArtistSplitProposals; // record contracts starting project ID // casting-up is safe startingProjectId = uint256(_startingProjectId); _updateProviderSalesAddresses( _renderProviderAddress, _renderProviderAddress, _platformProviderAddress, _platformProviderAddress ); _updateRandomizerAddress(_randomizerContract); // set AdminACL management contract as owner _transferOwnership(_adminACLContract); // initialize default base URI _updateDefaultBaseURI( string.concat( "https://token.artblocks.io/", toHexString(address(this)), "/" ) ); // initialize next project ID _nextProjectId = _startingProjectId; emit PlatformUpdated(FIELD_NEXT_PROJECT_ID); // @dev follow-on action: This contract does not self-register. A core // registry owner must register contract in a subsequent call. } /** * @notice Updates preferredIPFSGateway to `_gateway`. */ function updateIPFSGateway(string calldata _gateway) public { _onlyAdminACL(this.updateIPFSGateway.selector); preferredIPFSGateway = _gateway; emit GatewayUpdated(ExternalAssetDependencyType.IPFS, _gateway); } /** * @notice Updates preferredArweaveGateway to `_gateway`. */ function updateArweaveGateway(string calldata _gateway) public { _onlyAdminACL(this.updateArweaveGateway.selector); preferredArweaveGateway = _gateway; emit GatewayUpdated(ExternalAssetDependencyType.ARWEAVE, _gateway); } /** * @notice Locks external asset dependencies for project `_projectId`. */ function lockProjectExternalAssetDependencies(uint256 _projectId) external { _onlyUnlockedProjectExternalAssetDependencies(_projectId); _onlyArtistOrAdminACL( _projectId, this.lockProjectExternalAssetDependencies.selector ); projects[_projectId].externalAssetDependenciesLocked = true; emit ProjectExternalAssetDependenciesLocked(_projectId); } /** * @notice Updates external asset dependency for project `_projectId`. * @param _projectId Project to be updated. * @param _index Asset index. * @param _cidOrData Asset cid (Content identifier) or data string to be translated into bytecode. * @param _dependencyType Asset dependency type. * 0 - IPFS * 1 - ARWEAVE * 2 - ONCHAIN */ function updateProjectExternalAssetDependency( uint256 _projectId, uint256 _index, string memory _cidOrData, ExternalAssetDependencyType _dependencyType ) external { _onlyUnlockedProjectExternalAssetDependencies(_projectId); _onlyArtistOrAdminACL( _projectId, this.updateProjectExternalAssetDependency.selector ); uint24 assetCount = projects[_projectId].externalAssetDependencyCount; require(_index < assetCount, "Asset index out of range"); ExternalAssetDependency storage _oldDependency = projects[_projectId] .externalAssetDependencies[_index]; ExternalAssetDependencyType _oldDependencyType = _oldDependency .dependencyType; projects[_projectId] .externalAssetDependencies[_index] .dependencyType = _dependencyType; // if the incoming dependency type is onchain, we need to write the data to bytecode if (_dependencyType == ExternalAssetDependencyType.ONCHAIN) { if (_oldDependencyType != ExternalAssetDependencyType.ONCHAIN) { // we only need to set the cid to an empty string if we are replacing an offchain asset // an onchain asset will already have an empty cid projects[_projectId].externalAssetDependencies[_index].cid = ""; } projects[_projectId] .externalAssetDependencies[_index] .bytecodeAddress = _cidOrData.writeToBytecode(); // we don't want to emit data, so we emit the cid as an empty string _cidOrData = ""; } else { projects[_projectId] .externalAssetDependencies[_index] .cid = _cidOrData; } emit ExternalAssetDependencyUpdated( _projectId, _index, _cidOrData, _dependencyType, assetCount ); } /** * @notice Removes external asset dependency for project `_projectId` at index `_index`. * Removal is done by swapping the element to be removed with the last element in the array, then deleting this last element. * Assets with indices higher than `_index` can have their indices adjusted as a result of this operation. * @param _projectId Project to be updated. * @param _index Asset index */ function removeProjectExternalAssetDependency( uint256 _projectId, uint256 _index ) external { _onlyUnlockedProjectExternalAssetDependencies(_projectId); _onlyArtistOrAdminACL( _projectId, this.removeProjectExternalAssetDependency.selector ); uint24 assetCount = projects[_projectId].externalAssetDependencyCount; require(_index < assetCount, "Asset index out of range"); uint24 lastElementIndex = assetCount - 1; // copy last element to index of element to be removed projects[_projectId].externalAssetDependencies[_index] = projects[ _projectId ].externalAssetDependencies[lastElementIndex]; delete projects[_projectId].externalAssetDependencies[lastElementIndex]; projects[_projectId].externalAssetDependencyCount = lastElementIndex; emit ExternalAssetDependencyRemoved(_projectId, _index); } /** * @notice Adds external asset dependency for project `_projectId`. * @param _projectId Project to be updated. * @param _cidOrData Asset cid (Content identifier) or data string to be translated into bytecode. * @param _dependencyType Asset dependency type. * 0 - IPFS * 1 - ARWEAVE * 2 - ONCHAIN */ function addProjectExternalAssetDependency( uint256 _projectId, string memory _cidOrData, ExternalAssetDependencyType _dependencyType ) external { _onlyUnlockedProjectExternalAssetDependencies(_projectId); _onlyArtistOrAdminACL( _projectId, this.addProjectExternalAssetDependency.selector ); uint24 assetCount = projects[_projectId].externalAssetDependencyCount; address _bytecodeAddress = address(0); // if the incoming dependency type is onchain, we need to write the data to bytecode if (_dependencyType == ExternalAssetDependencyType.ONCHAIN) { _bytecodeAddress = _cidOrData.writeToBytecode(); // we don't want to emit data, so we emit the cid as an empty string _cidOrData = ""; } ExternalAssetDependency memory asset = ExternalAssetDependency({ cid: _cidOrData, dependencyType: _dependencyType, bytecodeAddress: _bytecodeAddress }); projects[_projectId].externalAssetDependencies[assetCount] = asset; projects[_projectId].externalAssetDependencyCount = assetCount + 1; emit ExternalAssetDependencyUpdated( _projectId, assetCount, _cidOrData, _dependencyType, assetCount + 1 ); } /** * @notice Mints a token from project `_projectId` and sets the * token's owner to `_to`. Hash may or may not be assigned to the token * during the mint transaction, depending on the randomizer contract. * @param _to Address to be the minted token's owner. * @param _projectId Project ID to mint a token on. * @param _by Purchaser of minted token. * @return _tokenId The ID of the minted token. * @dev sender must be the allowed minterContract * @dev name of function is optimized for gas usage */ function mint_Ecf( address _to, uint256 _projectId, address _by ) external returns (uint256 _tokenId) { // CHECKS require(msg.sender == minterContract, "Must mint from minter contract"); Project storage project = projects[_projectId]; // load invocations into memory uint24 invocationsBefore = project.invocations; uint24 invocationsAfter; unchecked { // invocationsBefore guaranteed <= maxInvocations <= 1_000_000, // 1_000_000 << max uint24, so no possible overflow invocationsAfter = invocationsBefore + 1; } uint24 maxInvocations = project.maxInvocations; require( invocationsBefore < maxInvocations, "Must not exceed max invocations" ); require( project.active || _by == projectIdToFinancials[_projectId].artistAddress, "Project must exist and be active" ); require( !project.paused || _by == projectIdToFinancials[_projectId].artistAddress, "Purchases are paused." ); // EFFECTS // increment project's invocations project.invocations = invocationsAfter; uint256 thisTokenId; unchecked { // invocationsBefore is uint24 << max uint256. In production use, // _projectId * ONE_MILLION must be << max uint256, otherwise // tokenIdToProjectId function become invalid. // Therefore, no risk of overflow thisTokenId = (_projectId * ONE_MILLION) + invocationsBefore; } // mark project as completed if hit max invocations if (invocationsAfter == maxInvocations) { _completeProject(_projectId); } // INTERACTIONS _mint(_to, thisTokenId); // token hash is updated by the randomizer contract on V3 randomizerContract.assignTokenHash(thisTokenId); // Do not need to also log `projectId` in event, as the `projectId` for // a given token can be derived from the `tokenId` with: // projectId = tokenId / 1_000_000 emit Mint(_to, thisTokenId); return thisTokenId; } /** * @notice Sets the hash seed for a given token ID `_tokenId`. * May only be called by the current randomizer contract. * May only be called for tokens that have not already been assigned a * non-zero hash. * @param _tokenId Token ID to set the hash for. * @param _hashSeed Hash seed to set for the token ID. Only last 12 bytes * will be used. * @dev gas-optimized function name because called during mint sequence * @dev if a separate event is required when the token hash is set, e.g. * for indexing purposes, it must be emitted by the randomizer. This is to * minimize gas when minting. */ function setTokenHash_8PT(uint256 _tokenId, bytes32 _hashSeed) external { _onlyValidTokenId(_tokenId); OwnerAndHashSeed storage ownerAndHashSeed = _ownersAndHashSeeds[ _tokenId ]; require( msg.sender == address(randomizerContract), "Only randomizer may set" ); require( ownerAndHashSeed.hashSeed == bytes12(0), "Token hash already set" ); require(_hashSeed != bytes12(0), "No zero hash seed"); ownerAndHashSeed.hashSeed = bytes12(_hashSeed); } /** * @notice Allows owner (AdminACL) to revoke ownership of the contract. * Note that the contract is intended to continue to function after the * owner renounces ownership, but no new projects will be able to be added. * Renouncing ownership will leave the contract without an owner, * thereby removing any functionality that is only available to the * owner/AdminACL contract. The same is true for any dependent contracts * that also integrate with the owner/AdminACL contract (e.g. potentially * minter suite contracts, registry contracts, etc.). * After renouncing ownership, artists will be in control of updates to * their payment addresses and splits (see modifier * onlyAdminACLOrRenouncedArtist`). * While there is no currently intended reason to call this method based on * typical Engine partner business practices, this method exists to allow * artists to continue to maintain the limited set of contract * functionality that exists post-project-lock in an environment in which * there is no longer an admin maintaining this smart contract. * @dev This function is intended to be called directly by the AdminACL, * not by an address allowed by the AdminACL contract. */ function renounceOwnership() public override onlyOwner { // broadcast that new projects are no longer allowed (if not already) _forbidNewProjects(); // renounce ownership viw Ownable Ownable.renounceOwnership(); } /** * @notice Updates reference to Art Blocks Dependency Registry contract. * @param _artblocksDependencyRegistryAddress Address of new Dependency * Registry. */ function updateArtblocksDependencyRegistryAddress( address _artblocksDependencyRegistryAddress ) external { _onlyAdminACL(this.updateArtblocksDependencyRegistryAddress.selector); _onlyNonZeroAddress(_artblocksDependencyRegistryAddress); artblocksDependencyRegistryAddress = _artblocksDependencyRegistryAddress; emit PlatformUpdated(FIELD_ARTBLOCKS_DEPENDENCY_REGISTRY_ADDRESS); } /** * @notice Updates sales addresses for the platform and render providers to * the input parameters. * @param _renderProviderPrimarySalesAddress Address of new primary sales * payment address. * @param _renderProviderSecondarySalesAddress Address of new secondary sales * payment address. * @param _platformProviderPrimarySalesAddress Address of new primary sales * payment address. * @param _platformProviderSecondarySalesAddress Address of new secondary sales * payment address. */ function updateProviderSalesAddresses( address payable _renderProviderPrimarySalesAddress, address payable _renderProviderSecondarySalesAddress, address payable _platformProviderPrimarySalesAddress, address payable _platformProviderSecondarySalesAddress ) external { _onlyAdminACL(this.updateProviderSalesAddresses.selector); _onlyNonZeroAddress(_renderProviderPrimarySalesAddress); _onlyNonZeroAddress(_renderProviderSecondarySalesAddress); _onlyNonZeroAddress(_platformProviderPrimarySalesAddress); _onlyNonZeroAddress(_platformProviderSecondarySalesAddress); _updateProviderSalesAddresses( _renderProviderPrimarySalesAddress, _renderProviderSecondarySalesAddress, _platformProviderPrimarySalesAddress, _platformProviderSecondarySalesAddress ); } /** * @notice Updates the render and platform provider primary sales revenue percentage to * the provided inputs. * @param renderProviderPrimarySalesPercentage_ New primary sales revenue % for the render provider * @param platformProviderPrimarySalesPercentage_ New primary sales revenue % for the platform provider * percentage. */ function updateProviderPrimarySalesPercentages( uint256 renderProviderPrimarySalesPercentage_, uint256 platformProviderPrimarySalesPercentage_ ) external { _onlyAdminACL(this.updateProviderPrimarySalesPercentages.selector); // Validate that the sum of the proposed %s, does not exceed 100%. require( (renderProviderPrimarySalesPercentage_ + platformProviderPrimarySalesPercentage_) <= ONE_HUNDRED, "Max sum of ONE_HUNDRED %" ); // Casting to `uint8` here is safe due check above, which does not allow // overflow as of solidity version ^0.8.0. _renderProviderPrimarySalesPercentage = uint8( renderProviderPrimarySalesPercentage_ ); _platformProviderPrimarySalesPercentage = uint8( platformProviderPrimarySalesPercentage_ ); emit PlatformUpdated(FIELD_PROVIDER_PRIMARY_SALES_PERCENTAGES); } /** * @notice Updates render and platform provider secondary sales royalty Basis Points to * the provided inputs. * @param _renderProviderSecondarySalesBPS New secondary sales royalty Basis * points. * @param _platformProviderSecondarySalesBPS New secondary sales royalty Basis * points. * @dev Due to secondary royalties being ultimately enforced via social * consensus, no hard upper limit is imposed on the BPS value, other than * <= 100% royalty, which would not make mathematical sense. Realistically, * changing this value is expected to either never occur, or be a rare * occurrence. */ function updateProviderSecondarySalesBPS( uint256 _renderProviderSecondarySalesBPS, uint256 _platformProviderSecondarySalesBPS ) external { _onlyAdminACL(this.updateProviderSecondarySalesBPS.selector); // Validate that the sum of the proposed provider BPS, does not exceed 10_000 BPS. require( (_renderProviderSecondarySalesBPS + _platformProviderSecondarySalesBPS) <= MAX_PROVIDER_SECONDARY_SALES_BPS, "Over max sum of BPS" ); renderProviderSecondarySalesBPS = _renderProviderSecondarySalesBPS; platformProviderSecondarySalesBPS = _platformProviderSecondarySalesBPS; emit PlatformUpdated(FIELD_PROVIDER_SECONDARY_SALES_BPS); } /** * @notice Updates minter to `_address`. * @param _address Address of new minter. */ function updateMinterContract(address _address) external { _onlyAdminACL(this.updateMinterContract.selector); _onlyNonZeroAddress(_address); minterContract = _address; emit MinterUpdated(_address); } /** * @notice Updates randomizer to `_randomizerAddress`. * @param _randomizerAddress Address of new randomizer. */ function updateRandomizerAddress(address _randomizerAddress) external { _onlyAdminACL(this.updateRandomizerAddress.selector); _onlyNonZeroAddress(_randomizerAddress); _updateRandomizerAddress(_randomizerAddress); } /** * @notice Toggles project `_projectId` as active/inactive. * @param _projectId Project ID to be toggled. */ function toggleProjectIsActive(uint256 _projectId) external { _onlyAdminACL(this.toggleProjectIsActive.selector); _onlyValidProjectId(_projectId); projects[_projectId].active = !projects[_projectId].active; emit ProjectUpdated(_projectId, FIELD_PROJECT_ACTIVE); } /** * @notice Artist proposes updated set of artist address, additional payee * addresses, and percentage splits for project `_projectId`. Addresses and * percentages do not have to all be changed, but they must all be defined * as a complete set. * Note that if the artist is only proposing a change to the payee percentage * splits, without modifying the payee addresses, the proposal will be * automatically approved and the new splits will become active immediately. * Automatic approval will also be granted if the artist is only removing * additional payee addresses, without adding any new ones. * Also note that if `autoApproveArtistSplitProposals` is true, proposals * will always be auto-approved, regardless of what is being changed. * Also note that if the artist is proposing sending funds to the zero * address, this function will revert and the proposal will not be created. * @param _projectId Project ID. * @param _artistAddress Artist address that controls the project, and may * receive payments. * @param _additionalPayeePrimarySales Address that may receive a * percentage split of the artist's primary sales revenue. * @param _additionalPayeePrimarySalesPercentage Percent of artist's * portion of primary sale revenue that will be split to address * `_additionalPayeePrimarySales`. * @param _additionalPayeeSecondarySales Address that may receive a percentage * split of the secondary sales royalties. * @param _additionalPayeeSecondarySalesPercentage Percent of artist's portion * of secondary sale royalties that will be split to address * `_additionalPayeeSecondarySales`. * @dev `_artistAddress` must be a valid address (non-zero-address), but it * is intentionally allowable for `_additionalPayee{Primary,Secondaary}Sales` * and their associated percentages to be zero'd out by the controlling artist. */ function proposeArtistPaymentAddressesAndSplits( uint256 _projectId, address payable _artistAddress, address payable _additionalPayeePrimarySales, uint256 _additionalPayeePrimarySalesPercentage, address payable _additionalPayeeSecondarySales, uint256 _additionalPayeeSecondarySalesPercentage ) external { _onlyValidProjectId(_projectId); _onlyArtist(_projectId); _onlyNonZeroAddress(_artistAddress); ProjectFinance storage projectFinance = projectIdToFinancials[ _projectId ]; // checks require( _additionalPayeePrimarySalesPercentage <= ONE_HUNDRED && _additionalPayeeSecondarySalesPercentage <= ONE_HUNDRED, "Max of 100%" ); require( _additionalPayeePrimarySalesPercentage == 0 || _additionalPayeePrimarySales != address(0), "Primary payee is zero address" ); require( _additionalPayeeSecondarySalesPercentage == 0 || _additionalPayeeSecondarySales != address(0), "Secondary payee is zero address" ); // effects // emit event for off-chain indexing // note: always emit a proposal event, even in the pathway of // automatic approval, to simplify indexing expectations emit ProposedArtistAddressesAndSplits( _projectId, _artistAddress, _additionalPayeePrimarySales, _additionalPayeePrimarySalesPercentage, _additionalPayeeSecondarySales, _additionalPayeeSecondarySalesPercentage ); // automatically accept if no proposed addresses modifications, or if // the proposal only removes payee addresses, or if contract is set to // always auto-approve. // store proposal hash on-chain, only if not automatic accept bool automaticAccept = autoApproveArtistSplitProposals; if (!automaticAccept) { // block scope to avoid stack too deep error bool artistUnchanged = _artistAddress == projectFinance.artistAddress; bool additionalPrimaryUnchangedOrRemoved = (_additionalPayeePrimarySales == projectFinance.additionalPayeePrimarySales) || (_additionalPayeePrimarySales == address(0)); bool additionalSecondaryUnchangedOrRemoved = (_additionalPayeeSecondarySales == projectFinance.additionalPayeeSecondarySales) || (_additionalPayeeSecondarySales == address(0)); automaticAccept = artistUnchanged && additionalPrimaryUnchangedOrRemoved && additionalSecondaryUnchangedOrRemoved; } if (automaticAccept) { // clear any previously proposed values proposedArtistAddressesAndSplitsHash[_projectId] = bytes32(0); // update storage // artist address can change during automatic accept if // autoApproveArtistSplitProposals is true projectFinance.artistAddress = _artistAddress; projectFinance .additionalPayeePrimarySales = _additionalPayeePrimarySales; // safe to cast as uint8 as max is 100%, max uint8 is 255 projectFinance.additionalPayeePrimarySalesPercentage = uint8( _additionalPayeePrimarySalesPercentage ); projectFinance .additionalPayeeSecondarySales = _additionalPayeeSecondarySales; // safe to cast as uint8 as max is 100%, max uint8 is 255 projectFinance.additionalPayeeSecondarySalesPercentage = uint8( _additionalPayeeSecondarySalesPercentage ); // emit event for off-chain indexing emit AcceptedArtistAddressesAndSplits(_projectId); } else { proposedArtistAddressesAndSplitsHash[_projectId] = keccak256( abi.encode( _artistAddress, _additionalPayeePrimarySales, _additionalPayeePrimarySalesPercentage, _additionalPayeeSecondarySales, _additionalPayeeSecondarySalesPercentage ) ); } } /** * @notice Admin accepts a proposed set of updated artist address, * additional payee addresses, and percentage splits for project * `_projectId`. Addresses and percentages do not have to all be changed, * but they must all be defined as a complete set. * @param _projectId Project ID. * @param _artistAddress Artist address that controls the project, and may * receive payments. * @param _additionalPayeePrimarySales Address that may receive a * percentage split of the artist's primary sales revenue. * @param _additionalPayeePrimarySalesPercentage Percent of artist's * portion of primary sale revenue that will be split to address * `_additionalPayeePrimarySales`. * @param _additionalPayeeSecondarySales Address that may receive a percentage * split of the secondary sales royalties. * @param _additionalPayeeSecondarySalesPercentage Percent of artist's portion * of secondary sale royalties that will be split to address * `_additionalPayeeSecondarySales`. * @dev this must be called by the Admin ACL contract, and must only accept * the most recent proposed values for a given project (validated on-chain * by comparing the hash of the proposed and accepted values). * @dev `_artistAddress` must be a valid address (non-zero-address), but it * is intentionally allowable for `_additionalPayee{Primary,Secondaary}Sales` * and their associated percentages to be zero'd out by the controlling artist. */ function adminAcceptArtistAddressesAndSplits( uint256 _projectId, address payable _artistAddress, address payable _additionalPayeePrimarySales, uint256 _additionalPayeePrimarySalesPercentage, address payable _additionalPayeeSecondarySales, uint256 _additionalPayeeSecondarySalesPercentage ) external { _onlyValidProjectId(_projectId); _onlyAdminACLOrRenouncedArtist( _projectId, this.adminAcceptArtistAddressesAndSplits.selector ); _onlyNonZeroAddress(_artistAddress); // checks require( proposedArtistAddressesAndSplitsHash[_projectId] == keccak256( abi.encode( _artistAddress, _additionalPayeePrimarySales, _additionalPayeePrimarySalesPercentage, _additionalPayeeSecondarySales, _additionalPayeeSecondarySalesPercentage ) ), "Must match artist proposal" ); // effects ProjectFinance storage projectFinance = projectIdToFinancials[ _projectId ]; projectFinance.artistAddress = _artistAddress; projectFinance .additionalPayeePrimarySales = _additionalPayeePrimarySales; projectFinance.additionalPayeePrimarySalesPercentage = uint8( _additionalPayeePrimarySalesPercentage ); projectFinance .additionalPayeeSecondarySales = _additionalPayeeSecondarySales; projectFinance.additionalPayeeSecondarySalesPercentage = uint8( _additionalPayeeSecondarySalesPercentage ); // clear proposed values proposedArtistAddressesAndSplitsHash[_projectId] = bytes32(0); // emit event for off-chain indexing emit AcceptedArtistAddressesAndSplits(_projectId); } /** * @notice Updates artist of project `_projectId` to `_artistAddress`. * This is to only be used in the event that the artist address is * compromised or sanctioned. * @param _projectId Project ID. * @param _artistAddress New artist address. */ function updateProjectArtistAddress( uint256 _projectId, address payable _artistAddress ) external { _onlyValidProjectId(_projectId); _onlyAdminACLOrRenouncedArtist( _projectId, this.updateProjectArtistAddress.selector ); _onlyNonZeroAddress(_artistAddress); projectIdToFinancials[_projectId].artistAddress = _artistAddress; emit ProjectUpdated(_projectId, FIELD_PROJECT_ARTIST_ADDRESS); } /** * @notice Toggles paused state of project `_projectId`. * @param _projectId Project ID to be toggled. */ function toggleProjectIsPaused(uint256 _projectId) external { _onlyArtist(_projectId); projects[_projectId].paused = !projects[_projectId].paused; emit ProjectUpdated(_projectId, FIELD_PROJECT_PAUSED); } /** * @notice Adds new project `_projectName` by `_artistAddress`. * @param _projectName Project name. * @param _artistAddress Artist's address. * @dev token price now stored on minter */ function addProject( string memory _projectName, address payable _artistAddress ) external { _onlyAdminACL(this.addProject.selector); _onlyNonEmptyString(_projectName); _onlyNonZeroAddress(_artistAddress); require(!newProjectsForbidden, "New projects forbidden"); uint256 projectId = _nextProjectId; projectIdToFinancials[projectId].artistAddress = _artistAddress; projects[projectId].name = _projectName; projects[projectId].paused = true; projects[projectId].maxInvocations = ONE_MILLION_UINT24; projects[projectId].projectBaseURI = defaultBaseURI; _nextProjectId = uint248(projectId) + 1; emit ProjectUpdated(projectId, FIELD_PROJECT_CREATED); } /** * @notice Forever forbids new projects from being added to this contract. */ function forbidNewProjects() external { _onlyAdminACL(this.forbidNewProjects.selector); require(!newProjectsForbidden, "Already forbidden"); _forbidNewProjects(); } /** * @notice Updates name of project `_projectId` to be `_projectName`. * @param _projectId Project ID. * @param _projectName New project name. */ function updateProjectName( uint256 _projectId, string memory _projectName ) external { _onlyUnlocked(_projectId); _onlyArtistOrAdminACL(_projectId, this.updateProjectName.selector); _onlyNonEmptyString(_projectName); projects[_projectId].name = _projectName; emit ProjectUpdated(_projectId, FIELD_PROJECT_NAME); } /** * @notice Updates artist name for project `_projectId` to be * `_projectArtistName`. * @param _projectId Project ID. * @param _projectArtistName New artist name. */ function updateProjectArtistName( uint256 _projectId, string memory _projectArtistName ) external { _onlyUnlocked(_projectId); _onlyArtistOrAdminACL( _projectId, this.updateProjectArtistName.selector ); _onlyNonEmptyString(_projectArtistName); projects[_projectId].artist = _projectArtistName; emit ProjectUpdated(_projectId, FIELD_PROJECT_ARTIST_NAME); } /** * @notice Updates artist secondary market royalties for project * `_projectId` to be `_secondMarketRoyalty` percent. * This DOES NOT include the secondary market royalty percentages collected * by the issuing platform; it is only the total percentage of royalties * that will be split to artist and additionalSecondaryPayee. * @param _projectId Project ID. * @param _secondMarketRoyalty Percent of secondary sales revenue that will * be split to artist and additionalSecondaryPayee. This must be less than * or equal to ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE percent. */ function updateProjectSecondaryMarketRoyaltyPercentage( uint256 _projectId, uint256 _secondMarketRoyalty ) external { _onlyArtist(_projectId); require( _secondMarketRoyalty <= ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE, "Over max percent" ); projectIdToFinancials[_projectId] .secondaryMarketRoyaltyPercentage = uint8(_secondMarketRoyalty); emit ProjectUpdated( _projectId, FIELD_PROJECT_SECONDARY_MARKET_ROYALTY_PERCENTAGE ); } /** * @notice Updates description of project `_projectId`. * Only artist may call when unlocked, only admin may call when locked. * Note: The BytecodeStorage library is used to store the description to * reduce initial upload cost, however, even minor edits will require an * expensive, entirely new bytecode storage contract to be deployed instead * of relatively cheap updates to already-warm storage slots. This results * in an increased gas cost for minor edits to the description after the * initial upload, but an overall decrease in gas cost for projects with * less than ~3-5 edits (depending on the length of the description). * @param _projectId Project ID. * @param _projectDescription New project description. */ function updateProjectDescription( uint256 _projectId, string memory _projectDescription ) external { // checks require( _projectUnlocked(_projectId) ? msg.sender == projectIdToFinancials[_projectId].artistAddress : adminACLAllowed( msg.sender, address(this), this.updateProjectDescription.selector ), "Only artist when unlocked, owner when locked" ); // effects // store description in contract bytecode, replacing reference address from // the old storage description with the newly created one projects[_projectId].descriptionAddress = _projectDescription .writeToBytecode(); emit ProjectUpdated(_projectId, FIELD_PROJECT_DESCRIPTION); } /** * @notice Updates website of project `_projectId` to be `_projectWebsite`. * @param _projectId Project ID. * @param _projectWebsite New project website. * @dev It is intentionally allowed for this to be set to the empty string. */ function updateProjectWebsite( uint256 _projectId, string memory _projectWebsite ) external { _onlyArtist(_projectId); projects[_projectId].website = _projectWebsite; emit ProjectUpdated(_projectId, FIELD_PROJECT_WEBSITE); } /** * @notice Updates license for project `_projectId`. * @param _projectId Project ID. * @param _projectLicense New project license. */ function updateProjectLicense( uint256 _projectId, string memory _projectLicense ) external { _onlyUnlocked(_projectId); _onlyArtistOrAdminACL(_projectId, this.updateProjectLicense.selector); _onlyNonEmptyString(_projectLicense); projects[_projectId].license = _projectLicense; emit ProjectUpdated(_projectId, FIELD_PROJECT_LICENSE); } /** * @notice Updates maximum invocations for project `_projectId` to * `_maxInvocations`. Maximum invocations may only be decreased by the * artist, and must be greater than or equal to current invocations. * New projects are created with maximum invocations of 1 million by * default. * @param _projectId Project ID. * @param _maxInvocations New maximum invocations. */ function updateProjectMaxInvocations( uint256 _projectId, uint24 _maxInvocations ) external { _onlyArtist(_projectId); // CHECKS Project storage project = projects[_projectId]; uint256 _invocations = project.invocations; require( (_maxInvocations < project.maxInvocations), "Only maxInvocations decrease" ); require(_maxInvocations >= _invocations, "Only gte invocations"); // EFFECTS project.maxInvocations = _maxInvocations; emit ProjectUpdated(_projectId, FIELD_PROJECT_MAX_INVOCATIONS); // register completed timestamp if action completed the project if (_maxInvocations == _invocations) { _completeProject(_projectId); } } /** * @notice Adds a script to project `_projectId`. * @param _projectId Project to be updated. * @param _script Script to be added. Required to be a non-empty string, * but no further validation is performed. */ function addProjectScript( uint256 _projectId, string memory _script ) external { _onlyUnlocked(_projectId); _onlyArtistOrAdminACL(_projectId, this.addProjectScript.selector); _onlyNonEmptyString(_script); Project storage project = projects[_projectId]; // store script in contract bytecode project.scriptBytecodeAddresses[project.scriptCount] = _script .writeToBytecode(); project.scriptCount = project.scriptCount + 1; emit ProjectUpdated(_projectId, FIELD_PROJECT_SCRIPT); } /** * @notice Updates script for project `_projectId` at script ID `_scriptId`. * @param _projectId Project to be updated. * @param _scriptId Script ID to be updated. * @param _script The updated script value. Required to be a non-empty * string, but no further validation is performed. */ function updateProjectScript( uint256 _projectId, uint256 _scriptId, string memory _script ) external { _onlyUnlocked(_projectId); _onlyArtistOrAdminACL(_projectId, this.updateProjectScript.selector); _onlyNonEmptyString(_script); Project storage project = projects[_projectId]; require(_scriptId < project.scriptCount, "scriptId out of range"); // store script in contract bytecode, replacing reference address from // the old storage contract with the newly created one project.scriptBytecodeAddresses[_scriptId] = _script.writeToBytecode(); emit ProjectUpdated(_projectId, FIELD_PROJECT_SCRIPT); } /** * @notice Removes last script from project `_projectId`. * @param _projectId Project to be updated. */ function removeProjectLastScript(uint256 _projectId) external { _onlyUnlocked(_projectId); _onlyArtistOrAdminACL( _projectId, this.removeProjectLastScript.selector ); Project storage project = projects[_projectId]; require(project.scriptCount > 0, "No scripts to remove"); // delete reference to old storage contract address delete project.scriptBytecodeAddresses[project.scriptCount - 1]; unchecked { project.scriptCount = project.scriptCount - 1; } emit ProjectUpdated(_projectId, FIELD_PROJECT_SCRIPT); } /** * @notice Updates script type for project `_projectId`. * @param _projectId Project to be updated. * @param _scriptTypeAndVersion Script type and version e.g. "[email protected]", * as bytes32 encoded string. */ function updateProjectScriptType( uint256 _projectId, bytes32 _scriptTypeAndVersion ) external { _onlyUnlocked(_projectId); _onlyArtistOrAdminACL( _projectId, this.updateProjectScriptType.selector ); Project storage project = projects[_projectId]; // require exactly one @ symbol in _scriptTypeAndVersion require( _scriptTypeAndVersion.containsExactCharacterQty( AT_CHARACTER_CODE, uint8(1) ), "must contain exactly one @" ); project.scriptTypeAndVersion = _scriptTypeAndVersion; emit ProjectUpdated(_projectId, FIELD_PROJECT_SCRIPT_TYPE); } /** * @notice Updates project's aspect ratio. * @param _projectId Project to be updated. * @param _aspectRatio Aspect ratio to be set. Intended to be string in the * format of a decimal, e.g. "1" for square, "1.77777778" for 16:9, etc., * allowing for a maximum of 10 digits and one (optional) decimal separator. */ function updateProjectAspectRatio( uint256 _projectId, string memory _aspectRatio ) external { _onlyUnlocked(_projectId); _onlyArtistOrAdminACL( _projectId, this.updateProjectAspectRatio.selector ); _onlyNonEmptyString(_aspectRatio); // Perform more detailed input validation for aspect ratio. bytes memory aspectRatioBytes = bytes(_aspectRatio); uint256 bytesLength = aspectRatioBytes.length; require(bytesLength <= 11, "Aspect ratio format too long"); bool hasSeenDecimalSeparator = false; bool hasSeenNumber = false; for (uint256 i; i < bytesLength; i++) { bytes1 character = aspectRatioBytes[i]; // Allow as many #s as desired. if (character >= 0x30 && character <= 0x39) { // 9-0 // We need to ensure there is at least 1 `9-0` occurrence. hasSeenNumber = true; continue; } if (character == 0x2E) { // . // Allow no more than 1 `.` occurrence. if (!hasSeenDecimalSeparator) { hasSeenDecimalSeparator = true; continue; } } revert("Improperly formatted aspect ratio"); } require(hasSeenNumber, "Aspect ratio has no numbers"); projects[_projectId].aspectRatio = _aspectRatio; emit ProjectUpdated(_projectId, FIELD_PROJECT_ASPECT_RATIO); } /** * @notice Updates base URI for project `_projectId` to `_newBaseURI`. * This is the controlling base URI for all tokens in the project. The * contract-level defaultBaseURI is only used when initializing new * projects. * @param _projectId Project to be updated. * @param _newBaseURI New base URI. */ function updateProjectBaseURI( uint256 _projectId, string memory _newBaseURI ) external { _onlyArtist(_projectId); _onlyNonEmptyString(_newBaseURI); projects[_projectId].projectBaseURI = _newBaseURI; emit ProjectUpdated(_projectId, FIELD_PROJECT_BASE_URI); } /** * @notice Updates default base URI to `_defaultBaseURI`. The * contract-level defaultBaseURI is only used when initializing new * projects. Token URIs are determined by their project's `projectBaseURI`. * @param _defaultBaseURI New default base URI. */ function updateDefaultBaseURI(string memory _defaultBaseURI) external { _onlyAdminACL(this.updateDefaultBaseURI.selector); _onlyNonEmptyString(_defaultBaseURI); _updateDefaultBaseURI(_defaultBaseURI); } /** * @notice Next project ID to be created on this contract. * @return uint256 Next project ID. */ function nextProjectId() external view returns (uint256) { return _nextProjectId; } /** * @notice Returns token hash for token ID `_tokenId`. Returns null if hash * has not been set. * @param _tokenId Token ID to be queried. * @return bytes32 Token hash. * @dev token hash is the keccak256 hash of the stored hash seed */ function tokenIdToHash(uint256 _tokenId) external view returns (bytes32) { bytes12 _hashSeed = _ownersAndHashSeeds[_tokenId].hashSeed; if (_hashSeed == 0) { return 0; } return keccak256(abi.encode(_hashSeed)); } /** * @notice Returns token hash **seed** for token ID `_tokenId`. Returns * null if hash seed has not been set. The hash seed id the bytes12 value * which is hashed to produce the token hash. * @param _tokenId Token ID to be queried. * @return bytes12 Token hash seed. * @dev token hash seed is keccak256 hashed to give the token hash */ function tokenIdToHashSeed( uint256 _tokenId ) external view returns (bytes12) { return _ownersAndHashSeeds[_tokenId].hashSeed; } /** * @notice View function returning the render provider portion of * primary sales, in percent. * @return uint256 The render provider portion of primary sales, * in percent. */ function renderProviderPrimarySalesPercentage() external view returns (uint256) { return _renderProviderPrimarySalesPercentage; } /** * @notice View function returning the platform provider portion of * primary sales, in percent. * @return uint256 The platform provider portion of primary sales, * in percent. */ function platformProviderPrimarySalesPercentage() external view returns (uint256) { return _platformProviderPrimarySalesPercentage; } /** * @notice View function returning Artist's address for project * `_projectId`. * @param _projectId Project ID to be queried. * @return address Artist's address. */ function projectIdToArtistAddress( uint256 _projectId ) external view returns (address payable) { return projectIdToFinancials[_projectId].artistAddress; } /** * @notice View function returning Artist's secondary market royalty * percentage for project `_projectId`. * This does not include render/platform providers portions of secondary * market royalties. * @param _projectId Project ID to be queried. * @return uint256 Artist's secondary market royalty percentage. */ function projectIdToSecondaryMarketRoyaltyPercentage( uint256 _projectId ) external view returns (uint256) { return projectIdToFinancials[_projectId].secondaryMarketRoyaltyPercentage; } /** * @notice View function returning Artist's additional payee address for * primary sales, for project `_projectId`. * @param _projectId Project ID to be queried. * @return address Artist's additional payee address for primary sales. */ function projectIdToAdditionalPayeePrimarySales( uint256 _projectId ) external view returns (address payable) { return projectIdToFinancials[_projectId].additionalPayeePrimarySales; } /** * @notice View function returning Artist's additional payee primary sales * percentage, for project `_projectId`. * @param _projectId Project ID to be queried. * @return uint256 Artist's additional payee primary sales percentage. */ function projectIdToAdditionalPayeePrimarySalesPercentage( uint256 _projectId ) external view returns (uint256) { return projectIdToFinancials[_projectId] .additionalPayeePrimarySalesPercentage; } /** * @notice View function returning Artist's additional payee address for * secondary sales, for project `_projectId`. * @param _projectId Project ID to be queried. * @return address payable Artist's additional payee address for secondary * sales. */ function projectIdToAdditionalPayeeSecondarySales( uint256 _projectId ) external view returns (address payable) { return projectIdToFinancials[_projectId].additionalPayeeSecondarySales; } /** * @notice View function returning Artist's additional payee secondary * sales percentage, for project `_projectId`. * @param _projectId Project ID to be queried. * @return uint256 Artist's additional payee secondary sales percentage. */ function projectIdToAdditionalPayeeSecondarySalesPercentage( uint256 _projectId ) external view returns (uint256) { return projectIdToFinancials[_projectId] .additionalPayeeSecondarySalesPercentage; } /** * @notice Returns project details for project `_projectId`. * @param _projectId Project to be queried. * @return projectName Name of project * @return artist Artist of project * @return description Project description * @return website Project website * @return license Project license * @dev this function was named projectDetails prior to V3 core contract. */ function projectDetails( uint256 _projectId ) external view returns ( string memory projectName, string memory artist, string memory description, string memory website, string memory license ) { Project storage project = projects[_projectId]; projectName = project.name; artist = project.artist; address projectDescriptionBytecodeAddress = project.descriptionAddress; if (projectDescriptionBytecodeAddress == address(0)) { description = ""; } else { description = _readFromBytecode(projectDescriptionBytecodeAddress); } website = project.website; license = project.license; } /** * @notice Returns project state data for project `_projectId`. * @param _projectId Project to be queried * @return invocations Current number of invocations * @return maxInvocations Maximum allowed invocations * @return active Boolean representing if project is currently active * @return paused Boolean representing if project is paused * @return completedTimestamp zero if project not complete, otherwise * timestamp of project completion. * @return locked Boolean representing if project is locked * @dev price and currency info are located on minter contracts */ function projectStateData( uint256 _projectId ) external view returns ( uint256 invocations, uint256 maxInvocations, bool active, bool paused, uint256 completedTimestamp, bool locked ) { Project storage project = projects[_projectId]; invocations = project.invocations; maxInvocations = project.maxInvocations; active = project.active; paused = project.paused; completedTimestamp = project.completedTimestamp; locked = !_projectUnlocked(_projectId); } /** * @notice Returns artist payment information for project `_projectId`. * @param _projectId Project to be queried * @return artistAddress Project Artist's address * @return additionalPayeePrimarySales Additional payee address for primary * sales * @return additionalPayeePrimarySalesPercentage Percentage of artist revenue * to be sent to the additional payee address for primary sales * @return additionalPayeeSecondarySales Additional payee address for secondary * sales royalties * @return additionalPayeeSecondarySalesPercentage Percentage of artist revenue * to be sent to the additional payee address for secondary sales royalties * @return secondaryMarketRoyaltyPercentage Royalty percentage to be sent to * combination of artist and additional payee. This does not include the * platform's percentage of secondary sales royalties, which is defined as * the sum of `renderProviderSecondarySalesBPS` * and `platformProviderSecondarySalesBPS`. */ function projectArtistPaymentInfo( uint256 _projectId ) external view returns ( address artistAddress, address additionalPayeePrimarySales, uint256 additionalPayeePrimarySalesPercentage, address additionalPayeeSecondarySales, uint256 additionalPayeeSecondarySalesPercentage, uint256 secondaryMarketRoyaltyPercentage ) { ProjectFinance storage projectFinance = projectIdToFinancials[ _projectId ]; artistAddress = projectFinance.artistAddress; additionalPayeePrimarySales = projectFinance .additionalPayeePrimarySales; additionalPayeePrimarySalesPercentage = projectFinance .additionalPayeePrimarySalesPercentage; additionalPayeeSecondarySales = projectFinance .additionalPayeeSecondarySales; additionalPayeeSecondarySalesPercentage = projectFinance .additionalPayeeSecondarySalesPercentage; secondaryMarketRoyaltyPercentage = projectFinance .secondaryMarketRoyaltyPercentage; } /** * @notice Returns script information for project `_projectId`. * @param _projectId Project to be queried. * @return scriptTypeAndVersion Project's script type and version * (e.g. "p5js(atSymbol)1.0.0") * @return aspectRatio Aspect ratio of project (e.g. "1" for square, * "1.77777778" for 16:9, etc.) * @return scriptCount Count of scripts for project */ function projectScriptDetails( uint256 _projectId ) external view override(IGenArt721CoreContractV3_Base, IDependencyRegistryCompatibleV0) returns ( string memory scriptTypeAndVersion, string memory aspectRatio, uint256 scriptCount ) { Project storage project = projects[_projectId]; scriptTypeAndVersion = project.scriptTypeAndVersion.toString(); aspectRatio = project.aspectRatio; scriptCount = project.scriptCount; } /** * @notice Returns address with bytecode containing project script for * project `_projectId` at script index `_index`. */ function projectScriptBytecodeAddressByIndex( uint256 _projectId, uint256 _index ) external view returns (address) { return projects[_projectId].scriptBytecodeAddresses[_index]; } /** * @notice Returns script for project `_projectId` at script index `_index`. * @param _projectId Project to be queried. * @param _index Index of script to be queried. */ function projectScriptByIndex( uint256 _projectId, uint256 _index ) external view returns (string memory) { Project storage project = projects[_projectId]; // If trying to access an out-of-index script, return the empty string. if (_index >= project.scriptCount) { return ""; } return _readFromBytecode(project.scriptBytecodeAddresses[_index]); } /** * @notice Returns base URI for project `_projectId`. * @param _projectId Project to be queried. * @return projectBaseURI Base URI for project */ function projectURIInfo( uint256 _projectId ) external view returns (string memory projectBaseURI) { projectBaseURI = projects[_projectId].projectBaseURI; } /** * @notice Backwards-compatible (pre-V3) function returning if `_minter` is * minterContract. * @param _minter Address to be queried. * @return bool Boolean representing if `_minter` is minterContract. */ function isMintWhitelisted(address _minter) external view returns (bool) { return (minterContract == _minter); } /** * @notice Gets qty of randomizers in history of all randomizers used by * this core contract. If a randomizer is switched away from then back to, * it will show up in the history twice. * @return randomizerHistoryCount Count of randomizers in history */ function numHistoricalRandomizers() external view returns (uint256) { return _historicalRandomizerAddresses.length; } /** * @notice Gets address of randomizer at index `_index` in history of all * randomizers used by this core contract. Index is zero-based. * @param _index Historical index of randomizer to be queried. * @return randomizerAddress Address of randomizer at index `_index`. * @dev If a randomizer is switched away from and then switched back to, it * will show up in the history twice. */ function getHistoricalRandomizerAt( uint256 _index ) external view returns (address) { require( _index < _historicalRandomizerAddresses.length, "Index out of bounds" ); return _historicalRandomizerAddresses[_index]; } /** * @notice Gets royalty Basis Points (BPS) for token ID `_tokenId`. * This conforms to the IManifold interface designated in the Royalty * Registry's RoyaltyEngineV1.sol contract. * ref: https://github.com/manifoldxyz/royalty-registry-solidity * @param _tokenId Token ID to be queried. * @return recipients Array of royalty payment recipients * @return bps Array of Basis Points (BPS) allocated to each recipient, * aligned by index. * @dev reverts if invalid _tokenId * @dev only returns recipients that have a non-zero BPS allocation */ function getRoyalties( uint256 _tokenId ) external view returns (address payable[] memory recipients, uint256[] memory bps) { _onlyValidTokenId(_tokenId); // initialize arrays with maximum potential length recipients = new address payable[](4); bps = new uint256[](4); uint256 projectId = tokenIdToProjectId(_tokenId); ProjectFinance storage projectFinance = projectIdToFinancials[ projectId ]; // load values into memory uint256 royaltyPercentageForArtistAndAdditional = projectFinance .secondaryMarketRoyaltyPercentage; uint256 additionalPayeePercentage = projectFinance .additionalPayeeSecondarySalesPercentage; // calculate BPS = percentage * 100 uint256 artistBPS = (ONE_HUNDRED - additionalPayeePercentage) * royaltyPercentageForArtistAndAdditional; uint256 additionalBPS = additionalPayeePercentage * royaltyPercentageForArtistAndAdditional; uint256 renderProviderBPS = renderProviderSecondarySalesBPS; uint256 platformProviderBPS = platformProviderSecondarySalesBPS; // populate arrays uint256 payeeCount; if (artistBPS > 0) { recipients[payeeCount] = projectFinance.artistAddress; bps[payeeCount++] = artistBPS; } if (additionalBPS > 0) { recipients[payeeCount] = projectFinance .additionalPayeeSecondarySales; bps[payeeCount++] = additionalBPS; } if (renderProviderBPS > 0) { recipients[payeeCount] = renderProviderSecondarySalesAddress; bps[payeeCount++] = renderProviderBPS; } if (platformProviderBPS > 0) { recipients[payeeCount] = platformProviderSecondarySalesAddress; bps[payeeCount++] = platformProviderBPS; } // trim arrays if necessary if (4 > payeeCount) { assembly { let decrease := sub(4, payeeCount) mstore(recipients, sub(mload(recipients), decrease)) mstore(bps, sub(mload(bps), decrease)) } } return (recipients, bps); } /** * @notice View function that returns appropriate revenue splits between * different render provider, platform provider, Artist, and Artist's * additional primary sales payee given a sale price of `_price` on * project `_projectId`. * This always returns four revenue amounts and four addresses, but if a * revenue is zero for either Artist or additional payee, the corresponding * address returned will also be null (for gas optimization). * Does not account for refund if user overpays for a token (minter should * handle a refund of the difference, if appropriate). * Some minters may have alternative methods of splitting payments, in * which case they should implement their own payment splitting logic. * @param _projectId Project ID to be queried. * @param _price Sale price of token. * @return renderProviderRevenue_ amount of revenue to be sent to the * render provider * @return renderProviderAddress_ address to send render provider revenue to * @return platformProviderRevenue_ amount of revenue to be sent to the * platform provider * @return platformProviderAddress_ address to send platform provider revenue to * @return artistRevenue_ amount of revenue to be sent to Artist * @return artistAddress_ address to send Artist revenue to. Will be null * if no revenue is due to artist (gas optimization). * @return additionalPayeePrimaryRevenue_ amount of revenue to be sent to * additional payee for primary sales * @return additionalPayeePrimaryAddress_ address to send Artist's * additional payee for primary sales revenue to. Will be null if no * revenue is due to additional payee for primary sales (gas optimization). * @dev this always returns four addresses and four revenues, but if the * revenue is zero, the corresponding address will be address(0). It is up * to the contract performing the revenue split to handle this * appropriately. */ function getPrimaryRevenueSplits( uint256 _projectId, uint256 _price ) external view returns ( uint256 renderProviderRevenue_, address payable renderProviderAddress_, uint256 platformProviderRevenue_, address payable platformProviderAddress_, uint256 artistRevenue_, address payable artistAddress_, uint256 additionalPayeePrimaryRevenue_, address payable additionalPayeePrimaryAddress_ ) { ProjectFinance storage projectFinance = projectIdToFinancials[ _projectId ]; // calculate revenues – this is a three-way split between the // render provider, the platform provider, and the artist, and // is safe to perform this given that in the case of loss of // precision Solidity will round down. uint256 projectFunds = _price; renderProviderRevenue_ = (_price * uint256(_renderProviderPrimarySalesPercentage)) / ONE_HUNDRED; // renderProviderRevenue_ percentage is always <=100, so guaranteed to never underflow projectFunds -= renderProviderRevenue_; platformProviderRevenue_ = (_price * uint256(_platformProviderPrimarySalesPercentage)) / ONE_HUNDRED; // platformProviderRevenue_ percentage is always <=100, so guaranteed to never underflow projectFunds -= platformProviderRevenue_; additionalPayeePrimaryRevenue_ = (projectFunds * projectFinance.additionalPayeePrimarySalesPercentage) / ONE_HUNDRED; // projectIdToAdditionalPayeePrimarySalesPercentage is always // <=100, so guaranteed to never underflow artistRevenue_ = projectFunds - additionalPayeePrimaryRevenue_; // set addresses from storage renderProviderAddress_ = renderProviderPrimarySalesAddress; platformProviderAddress_ = platformProviderPrimarySalesAddress; if (artistRevenue_ > 0) { artistAddress_ = projectFinance.artistAddress; } if (additionalPayeePrimaryRevenue_ > 0) { additionalPayeePrimaryAddress_ = projectFinance .additionalPayeePrimarySales; } } /** * @notice Returns external asset dependency for project `_projectId` at index `_index`. * If the dependencyType is ONCHAIN, the `data` field will contain the extrated bytecode data and `cid` * will be an empty string. Conversly, for any other dependencyType, the `data` field will be an empty string * and the `bytecodeAddress` will point to the zero address. */ function projectExternalAssetDependencyByIndex( uint256 _projectId, uint256 _index ) external view returns (ExternalAssetDependencyWithData memory) { ExternalAssetDependency storage _dependency = projects[_projectId] .externalAssetDependencies[_index]; address _bytecodeAddress = _dependency.bytecodeAddress; return ExternalAssetDependencyWithData({ dependencyType: _dependency.dependencyType, cid: _dependency.cid, bytecodeAddress: _bytecodeAddress, data: (_dependency.dependencyType == ExternalAssetDependencyType.ONCHAIN) ? _readFromBytecode(_bytecodeAddress) : "" }); } /** * @notice Returns external asset dependency count for project `_projectId` at index `_index`. */ function projectExternalAssetDependencyCount( uint256 _projectId ) external view returns (uint256) { return uint256(projects[_projectId].externalAssetDependencyCount); } /** * @notice Backwards-compatible (pre-V3) getter returning contract admin * @return address Address of contract admin (same as owner) */ function admin() external view returns (address) { return owner(); } /** * @notice Gets the project ID for a given `_tokenId`. * @param _tokenId Token ID to be queried. * @return _projectId Project ID for given `_tokenId`. */ function tokenIdToProjectId( uint256 _tokenId ) public pure returns (uint256 _projectId) { return _tokenId / ONE_MILLION; } /** * @notice Convenience function that returns whether `_sender` is allowed * to call function with selector `_selector` on contract `_contract`, as * determined by this contract's current Admin ACL contract. Expected use * cases include minter contracts checking if caller is allowed to call * admin-gated functions on minter contracts. * @param _sender Address of the sender calling function with selector * `_selector` on contract `_contract`. * @param _contract Address of the contract being called by `_sender`. * @param _selector Function selector of the function being called by * `_sender`. * @return bool Whether `_sender` is allowed to call function with selector * `_selector` on contract `_contract`. * @dev assumes the Admin ACL contract is the owner of this contract, which * is expected to always be true. * @dev adminACLContract is expected to either be null address (if owner * has renounced ownership), or conform to IAdminACLV0 interface. Check for * null address first to avoid revert when admin has renounced ownership. */ function adminACLAllowed( address _sender, address _contract, bytes4 _selector ) public returns (bool) { return owner() != address(0) && adminACLContract.allowed(_sender, _contract, _selector); } /** * @notice Returns contract owner. Set to deployer's address by default on * contract deployment. * @return address Address of contract owner. * @dev ref: https://docs.openzeppelin.com/contracts/4.x/api/access#Ownable * @dev owner role was called `admin` prior to V3 core contract */ function owner() public view override(Ownable, IGenArt721CoreContractV3_Base) returns (address) { return Ownable.owner(); } /** * @notice Gets token URI for token ID `_tokenId`. * @param _tokenId Token ID to be queried. * @return string URI of token ID `_tokenId`. * @dev token URIs are the concatenation of the project base URI and the * token ID. */ function tokenURI( uint256 _tokenId ) public view override returns (string memory) { _onlyValidTokenId(_tokenId); string memory _projectBaseURI = projects[tokenIdToProjectId(_tokenId)] .projectBaseURI; return string.concat(_projectBaseURI, toString(_tokenId)); } /** * @dev See {IERC165-supportsInterface}. */ function supportsInterface( bytes4 interfaceId ) public view virtual override returns (bool) { return interfaceId == type(IManifold).interfaceId || super.supportsInterface(interfaceId); } /** * @notice Forbids new projects from being created * @dev only performs operation and emits event if contract is not already * forbidding new projects. */ function _forbidNewProjects() internal { if (!newProjectsForbidden) { newProjectsForbidden = true; emit PlatformUpdated(FIELD_NEW_PROJECTS_FORBIDDEN); } } /** * @notice Transfers ownership of the contract to a new account (`newOwner`). * Internal function without access restriction. * @param newOwner New owner. * @dev owner role was called `admin` prior to V3 core contract. * @dev Overrides and wraps OpenZeppelin's _transferOwnership function to * also update adminACLContract for improved introspection. */ function _transferOwnership(address newOwner) internal override { Ownable._transferOwnership(newOwner); adminACLContract = IAdminACLV0(newOwner); } /** * @notice Updates sales addresses for the platform and render providers to * the input parameters. * @param _renderProviderPrimarySalesAddress Address of new primary sales * payment address. * @param _renderProviderSecondarySalesAddress Address of new secondary sales * payment address. * @param _platformProviderPrimarySalesAddress Address of new primary sales * payment address. * @param _platformProviderSecondarySalesAddress Address of new secondary sales * payment address. * @dev Note that this method does not check that the input address is * not `address(0)`, as it is expected that callers of this method should * perform input validation where applicable. */ function _updateProviderSalesAddresses( address _renderProviderPrimarySalesAddress, address _renderProviderSecondarySalesAddress, address _platformProviderPrimarySalesAddress, address _platformProviderSecondarySalesAddress ) internal { platformProviderPrimarySalesAddress = payable( _platformProviderPrimarySalesAddress ); platformProviderSecondarySalesAddress = payable( _platformProviderSecondarySalesAddress ); renderProviderPrimarySalesAddress = payable( _renderProviderPrimarySalesAddress ); renderProviderSecondarySalesAddress = payable( _renderProviderSecondarySalesAddress ); emit PlatformUpdated(FIELD_PROVIDER_SALES_ADDRESSES); } /** * @notice Updates randomizer address to `_randomizerAddress`. * @param _randomizerAddress New randomizer address. * @dev Note that this method does not check that the input address is * not `address(0)`, as it is expected that callers of this method should * perform input validation where applicable. */ function _updateRandomizerAddress(address _randomizerAddress) internal { randomizerContract = IRandomizer_V3CoreBase(_randomizerAddress); // populate historical randomizer array _historicalRandomizerAddresses.push(_randomizerAddress); emit PlatformUpdated(FIELD_RANDOMIZER_ADDRESS); } /** * @notice Updates default base URI to `_defaultBaseURI`. * When new projects are added, their `projectBaseURI` is automatically * initialized to `_defaultBaseURI`. * @param _defaultBaseURI New default base URI. * @dev Note that this method does not check that the input string is not * the empty string, as it is expected that callers of this method should * perform input validation where applicable. */ function _updateDefaultBaseURI(string memory _defaultBaseURI) internal { defaultBaseURI = _defaultBaseURI; emit PlatformUpdated(FIELD_DEFAULT_BASE_URI); } /** * @notice Internal function to complete a project. * @param _projectId Project ID to be completed. */ function _completeProject(uint256 _projectId) internal { projects[_projectId].completedTimestamp = uint64(block.timestamp); emit ProjectUpdated(_projectId, FIELD_PROJECT_COMPLETED); } /** * @notice Internal function that returns whether a project is unlocked. * Projects automatically lock four weeks after they are completed. * Projects are considered completed when they have been invoked the * maximum number of times. * @param _projectId Project ID to be queried. * @return bool true if project is unlocked, false otherwise. * @dev This also enforces that the `_projectId` passed in is valid. */ function _projectUnlocked(uint256 _projectId) internal view returns (bool) { _onlyValidProjectId(_projectId); uint256 projectCompletedTimestamp = projects[_projectId] .completedTimestamp; bool projectOpen = projectCompletedTimestamp == 0; return projectOpen || (block.timestamp - projectCompletedTimestamp < FOUR_WEEKS_IN_SECONDS); } /** * Helper for calling `BytecodeStorageReader` external library reader method, * added for bytecode size reduction purposes. */ function _readFromBytecode( address _address ) internal view returns (string memory) { return BytecodeStorageReader.readFromBytecode(_address); } // strings library from OpenZeppelin, modified for no constants bytes16 private _HEX_SYMBOLS = "0123456789abcdef"; uint8 private _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 with fixed length. */ function toHexString( uint256 value, uint256 length ) internal view 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, "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 view returns (string memory) { return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH); } } // SPDX-License-Identifier: LGPL-3.0-only // Created By: Art Blocks Inc. pragma solidity ^0.8.0; interface IAdminACLV0 { /** * @notice Token ID `_tokenId` minted to `_to`. * @param previousSuperAdmin The previous superAdmin address. * @param newSuperAdmin The new superAdmin address. * @param genArt721CoreAddressesToUpdate Array of genArt721Core * addresses to update to the new superAdmin, for indexing purposes only. */ event SuperAdminTransferred( address indexed previousSuperAdmin, address indexed newSuperAdmin, address[] genArt721CoreAddressesToUpdate ); /// Type of the Admin ACL contract, e.g. "AdminACLV0" function AdminACLType() external view returns (string memory); /// super admin address function superAdmin() external view returns (address); /** * @notice Calls transferOwnership on other contract from this contract. * This is useful for updating to a new AdminACL contract. * @dev this function should be gated to only superAdmin-like addresses. */ function transferOwnershipOn( address _contract, address _newAdminACL ) external; /** * @notice Calls renounceOwnership on other contract from this contract. * @dev this function should be gated to only superAdmin-like addresses. */ function renounceOwnershipOn(address _contract) external; /** * @notice Checks if sender `_sender` is allowed to call function with selector * `_selector` on contract `_contract`. */ function allowed( address _sender, address _contract, bytes4 _selector ) external returns (bool); } // SPDX-License-Identifier: LGPL-3.0-only // Created By: Art Blocks Inc. pragma solidity ^0.8.19; interface IDependencyRegistryCompatibleV0 { /// Dependency registry managed by Art Blocks function artblocksDependencyRegistryAddress() external view returns (address); /** * @notice Returns script information for project `_projectId`. * @param _projectId Project to be queried. * @return scriptTypeAndVersion Project's script type and version * (e.g. "p5js(atSymbol)1.0.0") * @return aspectRatio Aspect ratio of project (e.g. "1" for square, * "1.77777778" for 16:9, etc.) * @return scriptCount Count of scripts for project */ function projectScriptDetails( uint256 _projectId ) external view returns ( string memory scriptTypeAndVersion, string memory aspectRatio, uint256 scriptCount ); } // SPDX-License-Identifier: LGPL-3.0-only // Created By: Art Blocks Inc. pragma solidity ^0.8.0; interface IGenArt721CoreContractExposesHashSeed { // function to read the hash-seed for a given tokenId function tokenIdToHashSeed( uint256 _tokenId ) external view returns (bytes12); } // SPDX-License-Identifier: LGPL-3.0-only // Created By: Art Blocks Inc. pragma solidity ^0.8.0; import "./IAdminACLV0.sol"; /// use the Royalty Registry's IManifold interface for token royalties import "./IManifold.sol"; /** * @title This interface is intended to house interface items that are common * across all GenArt721CoreContractV3 flagship and derivative implementations. * This interface extends the IManifold royalty interface in order to * add support the Royalty Registry by default. * @author Art Blocks Inc. */ interface IGenArt721CoreContractV3_Base is IManifold { /** * @notice Token ID `_tokenId` minted to `_to`. */ event Mint(address indexed _to, uint256 indexed _tokenId); /** * @notice currentMinter updated to `_currentMinter`. * @dev Implemented starting with V3 core */ event MinterUpdated(address indexed _currentMinter); /** * @notice Platform updated on bytes32-encoded field `_field`. */ event PlatformUpdated(bytes32 indexed _field); /** * @notice Project ID `_projectId` updated on bytes32-encoded field * `_update`. */ event ProjectUpdated(uint256 indexed _projectId, bytes32 indexed _update); event ProposedArtistAddressesAndSplits( uint256 indexed _projectId, address _artistAddress, address _additionalPayeePrimarySales, uint256 _additionalPayeePrimarySalesPercentage, address _additionalPayeeSecondarySales, uint256 _additionalPayeeSecondarySalesPercentage ); event AcceptedArtistAddressesAndSplits(uint256 indexed _projectId); // version and type of the core contract // coreVersion is a string of the form "0.x.y" function coreVersion() external view returns (string memory); // coreType is a string of the form "GenArt721CoreV3" function coreType() external view returns (string memory); // owner (pre-V3 was named admin) of contract // this is expected to be an Admin ACL contract for V3 function owner() external view returns (address); // Admin ACL contract for V3, will be at the address owner() function adminACLContract() external returns (IAdminACLV0); // backwards-compatible (pre-V3) admin - equal to owner() function admin() external view returns (address); /** * Function determining if _sender is allowed to call function with * selector _selector on contract `_contract`. Intended to be used with * peripheral contracts such as minters, as well as internally by the * core contract itself. */ function adminACLAllowed( address _sender, address _contract, bytes4 _selector ) external returns (bool); /// getter function of public variable function startingProjectId() external view returns (uint256); // getter function of public variable function nextProjectId() external view returns (uint256); // getter function of public mapping function tokenIdToProjectId( uint256 tokenId ) external view returns (uint256 projectId); // @dev this is not available in V0 function isMintWhitelisted(address minter) external view returns (bool); function projectIdToArtistAddress( uint256 _projectId ) external view returns (address payable); function projectIdToAdditionalPayeePrimarySales( uint256 _projectId ) external view returns (address payable); function projectIdToAdditionalPayeePrimarySalesPercentage( uint256 _projectId ) external view returns (uint256); function projectIdToSecondaryMarketRoyaltyPercentage( uint256 _projectId ) external view returns (uint256); function projectURIInfo( uint256 _projectId ) external view returns (string memory projectBaseURI); // @dev new function in V3 function projectStateData( uint256 _projectId ) external view returns ( uint256 invocations, uint256 maxInvocations, bool active, bool paused, uint256 completedTimestamp, bool locked ); function projectDetails( uint256 _projectId ) external view returns ( string memory projectName, string memory artist, string memory description, string memory website, string memory license ); function projectScriptDetails( uint256 _projectId ) external view returns ( string memory scriptTypeAndVersion, string memory aspectRatio, uint256 scriptCount ); function projectScriptByIndex( uint256 _projectId, uint256 _index ) external view returns (string memory); function tokenIdToHash(uint256 _tokenId) external view returns (bytes32); // function to set a token's hash (must be guarded) function setTokenHash_8PT(uint256 _tokenId, bytes32 _hash) external; // @dev gas-optimized signature in V3 for `mint` function mint_Ecf( address _to, uint256 _projectId, address _by ) external returns (uint256 tokenId); } // SPDX-License-Identifier: LGPL-3.0-only // Created By: Art Blocks Inc. pragma solidity ^0.8.0; import "./IAdminACLV0.sol"; import "./IGenArt721CoreContractV3_Engine.sol"; /** * @title This interface is intended to house interface items that are common * across all GenArt721CoreContractV3 Engine Flex and derivative implementations. * @author Art Blocks Inc. */ interface IGenArt721CoreContractV3_Engine_Flex is IGenArt721CoreContractV3_Engine { /** * @notice When an external asset dependency is updated or added, this event is emitted. * @param _projectId The project ID of the project that was updated. * @param _index The index of the external asset dependency that was updated. * @param _cid The content ID of the external asset dependency. This is an empty string * if the dependency type is ONCHAIN. * @param _dependencyType The type of the external asset dependency. * @param _externalAssetDependencyCount The number of external asset dependencies. */ event ExternalAssetDependencyUpdated( uint256 indexed _projectId, uint256 indexed _index, string _cid, ExternalAssetDependencyType _dependencyType, uint24 _externalAssetDependencyCount ); /** * @notice The project id `_projectId` has had an external asset dependency removed at index `_index`. */ event ExternalAssetDependencyRemoved( uint256 indexed _projectId, uint256 indexed _index ); /** * @notice The preferred gateway for dependency type `_dependencyType` has been updated to `_gatewayAddress`. */ event GatewayUpdated( ExternalAssetDependencyType indexed _dependencyType, string _gatewayAddress ); /** * @notice The project id `_projectId` has had all external asset dependencies locked. * @dev This is a one-way operation. Once locked, the external asset dependencies cannot be updated. */ event ProjectExternalAssetDependenciesLocked(uint256 indexed _projectId); /** * @notice An external asset dependency type. Can be one of IPFS, ARWEAVE, or ONCHAIN. */ enum ExternalAssetDependencyType { IPFS, ARWEAVE, ONCHAIN } /** * @notice An external asset dependency. This is a struct that contains the CID of the dependency, * the type of the dependency, and the address of the bytecode for this dependency. */ struct ExternalAssetDependency { string cid; ExternalAssetDependencyType dependencyType; address bytecodeAddress; } /** * @notice An external asset dependency with data. This is a convenience struct that contains the CID of the dependency, * the type of the dependency, the address of the bytecode for this dependency, and the data retrieved from this bytecode address. */ struct ExternalAssetDependencyWithData { string cid; ExternalAssetDependencyType dependencyType; address bytecodeAddress; string data; } // preferredIPFSGateway is a url string function preferredIPFSGateway() external view returns (string memory); // preferredArweaveGateway is a url string function preferredArweaveGateway() external view returns (string memory); // updates the preferred IPFS gateway function updateIPFSGateway(string calldata _gateway) external; // updates the preferred Arweave gateway function updateArweaveGateway(string calldata _gateway) external; // locks the external asset dependencies for a project function lockProjectExternalAssetDependencies(uint256 _projectId) external; // updates the external asset dependency for a project at a given index function updateProjectExternalAssetDependency( uint256 _projectId, uint256 _index, string memory _cidOrData, ExternalAssetDependencyType _dependencyType ) external; // adds an external asset dependency for a project function addProjectExternalAssetDependency( uint256 _projectId, string memory _cidOrData, ExternalAssetDependencyType _dependencyType ) external; // removes an external asset dependency for a project at a given index function removeProjectExternalAssetDependency( uint256 _projectId, uint256 _index ) external; // getter function for project external asset dependencies function projectExternalAssetDependencyByIndex( uint256 _projectId, uint256 _index ) external view returns (ExternalAssetDependencyWithData memory); // getter function project external asset dependency count function projectExternalAssetDependencyCount( uint256 _projectId ) external view returns (uint256); } // SPDX-License-Identifier: LGPL-3.0-only // Created By: Art Blocks Inc. pragma solidity ^0.8.0; import "./IAdminACLV0.sol"; import "./IGenArt721CoreContractV3_Base.sol"; interface IGenArt721CoreContractV3_Engine is IGenArt721CoreContractV3_Base { // @dev new function in V3 function getPrimaryRevenueSplits( uint256 _projectId, uint256 _price ) external view returns ( uint256 renderProviderRevenue_, address payable renderProviderAddress_, uint256 platformProviderRevenue_, address payable platformProviderAddress_, uint256 artistRevenue_, address payable artistAddress_, uint256 additionalPayeePrimaryRevenue_, address payable additionalPayeePrimaryAddress_ ); // @dev The render provider primary sales payment address function renderProviderPrimarySalesAddress() external view returns (address payable); // @dev The platform provider primary sales payment address function platformProviderPrimarySalesAddress() external view returns (address payable); // @dev Percentage of primary sales allocated to the render provider function renderProviderPrimarySalesPercentage() external view returns (uint256); // @dev Percentage of primary sales allocated to the platform provider function platformProviderPrimarySalesPercentage() external view returns (uint256); // @dev The render provider secondary sales royalties payment address function renderProviderSecondarySalesAddress() external view returns (address payable); // @dev The platform provider secondary sales royalties payment address function platformProviderSecondarySalesAddress() external view returns (address payable); // @dev Basis points of secondary sales allocated to the render provider function renderProviderSecondarySalesBPS() external view returns (uint256); // @dev Basis points of secondary sales allocated to the platform provider function platformProviderSecondarySalesBPS() external view returns (uint256); } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /// @dev Royalty Registry interface, used to support the Royalty Registry. /// @dev Source: https://github.com/manifoldxyz/royalty-registry-solidity/blob/main/contracts/specs/IManifold.sol /// @author: manifold.xyz /** * @dev Royalty interface for creator core classes */ interface IManifold { /** * @dev Get royalites of a token. Returns list of receivers and basisPoints * * bytes4(keccak256('getRoyalties(uint256)')) == 0xbb3bafd6 * * => 0xbb3bafd6 = 0xbb3bafd6 */ function getRoyalties( uint256 tokenId ) external view returns (address payable[] memory, uint256[] memory); } // SPDX-License-Identifier: LGPL-3.0-only // Creatd By: Art Blocks Inc. pragma solidity ^0.8.0; interface IRandomizer_V3CoreBase { /** * @notice This function is intended to be called by a core contract, and * the core contract can be assured that the randomizer will call back to * the calling contract to set the token hash seed for `_tokenId` via * `setTokenHash_8PT`. * @dev This function may revert if hash seed generation is improperly * configured (for example, if in polyptych mode, but no hash seed has been * previously configured). * @dev This function is not specifically gated to any specific caller, but * will only call back to the calling contract, `msg.sender`, to set the * specified token's hash seed. * A third party contract calling this function will not be able to set the * token hash seed on a different core contract. * @param _tokenId The token ID must be assigned a hash. */ function assignTokenHash(uint256 _tokenId) external; } // SPDX-License-Identifier: LGPL-3.0-only // Created By: Art Blocks Inc. pragma solidity ^0.8.0; /** * @title Art Blocks Script Storage Library * @notice Utilize contract bytecode as persistent storage for large chunks of script string data. * This library is intended to have an external deployed copy that is released in the future, * and, as such, has been designed to support both updated V1 (versioned, with purging removed) * reads as well as backwards-compatible reads for both a) the unversioned "V0" storage contracts * which were deployed by the original version of this libary and b) contracts that were deployed * using one of the SSTORE2 implementations referenced below. * For these pre-V1 storage contracts (which themselves did not have any explicit versioning semantics) * backwards-compatible reads are optimistic, and only expected to work for contracts actually * deployed by the original version of this library – and may fail ungracefully if attempted to be * used to read from other contracts. * This library is split into two components, intended to be updated in tandem, and thus included * here in the same source file. One component is an internal library that is intended to be embedded * directly into other contracts and provides all _write_ functionality. The other is a public library * that is intended to be deployed as a standalone contract and provides all _read_ functionality. * * @author Art Blocks Inc. * @author Modified from 0xSequence (https://github.com/0xsequence/sstore2/blob/master/contracts/SSTORE2.sol) * @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SSTORE2.sol) * * @dev Compared to the above two rerferenced libraries, this contracts-as-storage implementation makes a few * notably different design decisions: * - uses the `string` data type for input/output on reads, rather than speaking in bytes directly * - stores the "writer" address (library user) in the deployed contract bytes, which is useful for * on-chain introspection and provenance purposes * - stores a very simple versioning string in the deployed contract bytes, which captures the version * of the library that was used to deploy the storage contract and useful for supporting future * compatibility management as this library evolves (e.g. in response to EOF v1 migration plans) * Also, given that much of this library is written in assembly, this library makes use of a slightly * different convention (when compared to the rest of the Art Blocks smart contract repo) around * pre-defining return values in some cases in order to simplify need to directly memory manage these * return values. */ /** * @title Art Blocks Script Storage Library (Public, Reads) * @author Art Blocks Inc. * @notice The public library for reading from storage contracts. This library is intended to be deployed as a * standalone contract, and provides all _read_ functionality. */ library BytecodeStorageReader { // Define the set of known valid version strings that may be stored in the deployed storage contract bytecode // note: These are all intentionally exactly 32-bytes and are null-terminated. Null-termination is used due // to this being the standard expected formatting in common web3 tooling such as ethers.js. Please see // the following for additional context: https://docs.ethers.org/v5/api/utils/strings/#Bytes32String // Used for storage contracts that were deployed by an unknown source bytes32 public constant UNKNOWN_VERSION_STRING = "UNKNOWN_VERSION_STRING_________ "; // Pre-dates versioning string, so this doesn't actually exist in any deployed contracts, // but is useful for backwards-compatible semantics with original version of this library bytes32 public constant V0_VERSION_STRING = "BytecodeStorage_V0.0.0_________ "; // The first versioned storage contract, deployed by an updated version of this library bytes32 public constant V1_VERSION_STRING = "BytecodeStorage_V1.0.0_________ "; // The current version of this library. bytes32 public constant CURRENT_VERSION = V1_VERSION_STRING; //---------------------------------------------------------------------------------------------------------------// // Starting Index | Size | Ending Index | Description // //---------------------------------------------------------------------------------------------------------------// // 0 | N/A | 0 | // // 0 | 1 | 1 | single byte opcode for making the storage contract non-executable // // 1 | 32 | 33 | the 32 byte slot used for storing a basic versioning string // // 33 | 32 | 65 | the 32 bytes for storing the deploying contract's (0-padded) address // //---------------------------------------------------------------------------------------------------------------// // Define the offset for where the "meta bytes" end, and the "data bytes" begin. Note that this is a manually // calculated value, and must be updated if the above table is changed. It is expected that tests will fail // loudly if these values are not updated in-step with eachother. uint256 private constant VERSION_OFFSET = 1; uint256 private constant ADDRESS_OFFSET = 33; uint256 private constant DATA_OFFSET = 65; // Define the set of known *historic* offset values for where the "meta bytes" end, and the "data bytes" begin. // SSTORE2 deployed storage contracts take the general format of: // concat(0x00, data) // note: this is true for both variants of the SSTORE2 library uint256 private constant SSTORE2_DATA_OFFSET = 1; // V0 deployed storage contracts take the general format of: // concat(gated-cleanup-logic, deployer-address, data) uint256 private constant V0_ADDRESS_OFFSET = 72; uint256 private constant V0_DATA_OFFSET = 104; // V1 deployed storage contracts take the general format of: // concat(invalid opcode, version, deployer-address, data) uint256 private constant V1_ADDRESS_OFFSET = ADDRESS_OFFSET; uint256 private constant V1_DATA_OFFSET = DATA_OFFSET; /*////////////////////////////////////////////////////////////// READ LOGIC //////////////////////////////////////////////////////////////*/ /** * @notice Read a string from contract bytecode * @param _address address of deployed contract with bytecode stored in the V0 or V1 format * @return data string read from contract bytecode * @dev This function performs input validation that the contract to read is in an expected format */ function readFromBytecode( address _address ) public view returns (string memory data) { uint256 dataOffset = _bytecodeDataOffsetAt(_address); return string(readBytesFromBytecode(_address, dataOffset)); } /** * @notice Read the bytes from contract bytecode that was written to the EVM using SSTORE2 * @param _address address of deployed contract with bytecode stored in the SSTORE2 format * @return data bytes read from contract bytecode * @dev This function performs no input validation on the provided contract, * other than that there is content to read (but not that its a "storage contract") */ function readBytesFromSSTORE2Bytecode( address _address ) public view returns (bytes memory data) { return readBytesFromBytecode(_address, SSTORE2_DATA_OFFSET); } /** * @notice Read the bytes from contract bytecode, with an explicitly provided starting offset * @param _address address of deployed contract with bytecode stored in the V0 or V1 format * @param _offset offset to read from in contract bytecode, explicitly provided (not calculated) * @return data bytes read from contract bytecode * @dev This function performs no input validation on the provided contract, * other than that there is content to read (but not that its a "storage contract") */ function readBytesFromBytecode( address _address, uint256 _offset ) public view returns (bytes memory data) { // get the size of the bytecode uint256 bytecodeSize = _bytecodeSizeAt(_address); // handle case where address contains code < _offset if (bytecodeSize < _offset) { revert("ContractAsStorage: Read Error"); } // handle case where address contains code >= dataOffset // decrement by dataOffset to account for header info uint256 size; unchecked { size = bytecodeSize - _offset; } assembly { // allocate free memory data := mload(0x40) // update free memory pointer // use and(x, not(0x1f) as cheaper equivalent to sub(x, mod(x, 0x20)). // adding 0x1f to size + logic above ensures the free memory pointer // remains word-aligned, following the Solidity convention. mstore(0x40, add(data, and(add(add(size, 0x20), 0x1f), not(0x1f)))) // store length of data in first 32 bytes mstore(data, size) // copy code to memory, excluding the deployer-address extcodecopy(_address, add(data, 0x20), _offset, size) } } /** * @notice Get address for deployer for given contract bytecode * @param _address address of deployed contract with bytecode stored in the V0 or V1 format * @return writerAddress address read from contract bytecode */ function getWriterAddressForBytecode( address _address ) public view returns (address) { // get the size of the data uint256 bytecodeSize = _bytecodeSizeAt(_address); // the dataOffset for the bytecode uint256 addressOffset = _bytecodeAddressOffsetAt(_address); // handle case where address contains code < addressOffset + 32 (address takes a whole slot) if (bytecodeSize < (addressOffset + 32)) { revert("ContractAsStorage: Read Error"); } assembly { // allocate free memory let writerAddress := mload(0x40) // shift free memory pointer by one slot mstore(0x40, add(mload(0x40), 0x20)) // copy the 32-byte address of the data contract writer to memory // note: this relies on the assumption noted at the top-level of // this file that the storage layout for the deployed // contracts-as-storage contract looks like:: // | invalid opcode | version-string (unless v0) | deployer-address (padded) | data | extcodecopy( _address, writerAddress, addressOffset, 0x20 // full 32-bytes, as address is expected to be zero-padded ) return( writerAddress, 0x20 // return size is entire slot, as it is zero-padded ) } } /** * @notice Get version for given contract bytecode * @param _address address of deployed contract with bytecode stored in the V0 or V1 format * @return version version read from contract bytecode */ function getLibraryVersionForBytecode( address _address ) public view returns (bytes32) { return _bytecodeVersionAt(_address); } /*////////////////////////////////////////////////////////////// INTERNAL HELPER LOGIC //////////////////////////////////////////////////////////////*/ /** * @notice Returns the size of the bytecode at address `_address` * @param _address address that may or may not contain bytecode * @return size size of the bytecode code at `_address` */ function _bytecodeSizeAt( address _address ) private view returns (uint256 size) { assembly { size := extcodesize(_address) } if (size == 0) { revert("ContractAsStorage: Read Error"); } } /** * @notice Returns the offset of the data in the bytecode at address `_address` * @param _address address that may or may not contain bytecode * @return dataOffset offset of data in bytecode if a known version, otherwise 0 */ function _bytecodeDataOffsetAt( address _address ) private view returns (uint256 dataOffset) { bytes32 version = _bytecodeVersionAt(_address); if (version == V1_VERSION_STRING) { dataOffset = V1_DATA_OFFSET; } else if (version == V0_VERSION_STRING) { dataOffset = V0_DATA_OFFSET; } else { // unknown version, revert revert("ContractAsStorage: Unsupported Version"); } } /** * @notice Returns the offset of the address in the bytecode at address `_address` * @param _address address that may or may not contain bytecode * @return addressOffset offset of address in bytecode if a known version, otherwise 0 */ function _bytecodeAddressOffsetAt( address _address ) private view returns (uint256 addressOffset) { bytes32 version = _bytecodeVersionAt(_address); if (version == V1_VERSION_STRING) { addressOffset = V1_ADDRESS_OFFSET; } else if (version == V0_VERSION_STRING) { addressOffset = V0_ADDRESS_OFFSET; } else { // unknown version, revert revert("ContractAsStorage: Unsupported Version"); } } /** * @notice Get version string for given contract bytecode * @param _address address of deployed contract with bytecode stored in the V0 or V1 format * @return version version string read from contract bytecode */ function _bytecodeVersionAt( address _address ) private view returns (bytes32 version) { // get the size of the data uint256 bytecodeSize = _bytecodeSizeAt(_address); // handle case where address contains code < minimum expected version string size, // by returning early with the unknown version string if (bytecodeSize < (VERSION_OFFSET + 32)) { return UNKNOWN_VERSION_STRING; } assembly { // allocate free memory let versionString := mload(0x40) // shift free memory pointer by one slot mstore(0x40, add(mload(0x40), 0x20)) // copy the 32-byte version string of the bytecode library to memory // note: this relies on the assumption noted at the top-level of // this file that the storage layout for the deployed // contracts-as-storage contract looks like: // | invalid opcode | version-string (unless v0) | deployer-address (padded) | data | extcodecopy( _address, versionString, VERSION_OFFSET, 0x20 // 32-byte version string ) // note: must check against literal strings, as Yul does not allow for // dynamic strings in switch statements. switch mload(versionString) case "BytecodeStorage_V1.0.0_________ " { version := V1_VERSION_STRING } case 0x2060486000396000513314601057fe5b60013614601957fe5b6000357fff0000 { // the v0 variant of this library pre-dates formal versioning w/ version strings, // so we check the first 32 bytes of the execution bytecode itself which // is static and known across all storage contracts deployed with the first version // of this library. version := V0_VERSION_STRING } default { version := UNKNOWN_VERSION_STRING } } } } /** * @title Art Blocks Script Storage Library (Internal, Writes) * @author Art Blocks Inc. * @notice The internal library for writing to storage contracts. This library is intended to be deployed * within library client contracts that use this library to perform _write_ operations on storage. */ library BytecodeStorageWriter { /*////////////////////////////////////////////////////////////// WRITE LOGIC //////////////////////////////////////////////////////////////*/ /** * @notice Write a string to contract bytecode * @param _data string to be written to contract. No input validation is performed on this parameter. * @param address_ address of deployed contract with bytecode stored in the V0 or V1 format */ function writeToBytecode( string memory _data ) internal returns (address address_) { // prefix bytecode with bytes memory creationCode = abi.encodePacked( //---------------------------------------------------------------------------------------------------------------// // Opcode | Opcode + Arguments | Description | Stack View // //---------------------------------------------------------------------------------------------------------------// // a.) creation code returns all code in the contract except for the first 11 (0B in hex) bytes, as these 11 // bytes are the creation code itself which we do not want to store in the deployed storage contract result //---------------------------------------------------------------------------------------------------------------// // 0x60 | 0x60_0B | 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 | // //---------------------------------------------------------------------------------------------------------------// // (11 bytes) hex"60_0B_59_81_38_03_80_92_59_39_F3", //---------------------------------------------------------------------------------------------------------------// // b.) ensure that the deployed storage contract is non-executeable (first opcode is the `invalid` opcode) //---------------------------------------------------------------------------------------------------------------// //---------------------------------------------------------------------------------------------------------------// // 0xFE | 0xFE | INVALID | // //---------------------------------------------------------------------------------------------------------------// // (1 byte) hex"FE", //---------------------------------------------------------------------------------------------------------------// // c.) store the version string, which is already represented as a 32-byte value //---------------------------------------------------------------------------------------------------------------// // (32 bytes) BytecodeStorageReader.CURRENT_VERSION, //---------------------------------------------------------------------------------------------------------------// // d.) store the deploying-contract's address with 0-padding to fit a 20-byte address into a 32-byte slot //---------------------------------------------------------------------------------------------------------------// // (12 bytes) hex"00_00_00_00_00_00_00_00_00_00_00_00", // (20 bytes) address(this), // uploaded data (stored as bytecode) comes last _data ); assembly { // deploy a new contract with the generated creation code. // start 32 bytes into creationCode to avoid copying the byte length. address_ := create(0, add(creationCode, 0x20), mload(creationCode)) } // address must be non-zero if contract was deployed successfully require(address_ != address(0), "ContractAsStorage: Write Error"); } } // SPDX-License-Identifier: LGPL-3.0-only // Created By: Art Blocks Inc. // Inspired by: https://ethereum.stackexchange.com/a/123950/103422 pragma solidity ^0.8.0; /** * @dev Operations on bytes32 data type, dealing with conversion to string. */ library Bytes32Strings { /** * @dev Intended to convert a `bytes32`-encoded string literal to `string`. * Trims zero padding to arrive at original string literal. */ function toString( bytes32 source ) internal pure returns (string memory result) { uint8 length = 0; while (source[length] != 0 && length < 32) { length++; } assembly { // free memory pointer result := mload(0x40) // update free memory pointer to new "memory end" // (offset is 64-bytes: 32 for length, 32 for data) mstore(0x40, add(result, 0x40)) // store length in first 32-byte memory slot mstore(result, length) // write actual data in second 32-byte memory slot mstore(add(result, 0x20), source) } } /** * @dev Intended to check if a `bytes32`-encoded string contains a given * character with UTF-8 character code `utf8CharCode exactly `targetQty` * times. Does not support searching for multi-byte characters, only * characters with UTF-8 character codes < 0x80. */ function containsExactCharacterQty( bytes32 source, uint8 utf8CharCode, uint8 targetQty ) internal pure returns (bool) { uint8 _occurrences = 0; uint8 i; for (i = 0; i < 32; ) { uint8 _charCode = uint8(source[i]); // if not a null byte, or a multi-byte UTF-8 character, check match if (_charCode != 0 && _charCode < 0x80) { if (_charCode == utf8CharCode) { unchecked { // no risk of overflow since max 32 iterations < max uin8=255 ++_occurrences; } } } unchecked { // no risk of overflow since max 32 iterations < max uin8=255 ++i; } } return _occurrences == targetQty; } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/ERC721.sol) pragma solidity ^0.8.0; import "@openzeppelin-4.7/contracts/token/ERC721/IERC721.sol"; import "@openzeppelin-4.7/contracts/token/ERC721/IERC721Receiver.sol"; import "@openzeppelin-4.7/contracts/token/ERC721/extensions/IERC721Metadata.sol"; import "@openzeppelin-4.7/contracts/utils/Address.sol"; import "@openzeppelin-4.7/contracts/utils/Context.sol"; import "@openzeppelin-4.7/contracts/utils/Strings.sol"; import "@openzeppelin-4.7/contracts/utils/introspection/ERC165.sol"; /** * @dev Forked version of the OpenZeppelin v4.7.1 ERC721 contract. Utilizes a * struct to pack owner and hash seed into a single storage slot. * --------------------- * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including * the Metadata extension, but not including the Enumerable extension, which is available separately as * {ERC721Enumerable}. */ contract ERC721_PackedHashSeed is Context, ERC165, IERC721, IERC721Metadata { using Address for address; using Strings for uint256; // Token name string private _name; // Token symbol string private _symbol; /// struct to pack a token owner and hash seed into same storage slot struct OwnerAndHashSeed { // 20 bytes for address of token's owner address owner; // remaining 12 bytes allocated to token hash seed bytes12 hashSeed; } /// mapping of token ID to OwnerAndHashSeed /// @dev visibility internal so inheriting contracts can access mapping(uint256 => OwnerAndHashSeed) internal _ownersAndHashSeeds; // Mapping owner address to token count mapping(address => uint256) private _balances; // Mapping from token ID to approved address mapping(uint256 => address) private _tokenApprovals; // Mapping from owner to operator approvals mapping(address => mapping(address => bool)) private _operatorApprovals; /** * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. */ constructor(string memory name_, string memory symbol_) { _name = name_; _symbol = symbol_; } /** * @dev See {IERC165-supportsInterface}. */ function supportsInterface( bytes4 interfaceId ) public view virtual override(ERC165, IERC165) returns (bool) { return interfaceId == type(IERC721).interfaceId || interfaceId == type(IERC721Metadata).interfaceId || super.supportsInterface(interfaceId); } /** * @dev See {IERC721-balanceOf}. */ function balanceOf( address owner ) public view virtual override returns (uint256) { require( owner != address(0), "ERC721: address zero is not a valid owner" ); return _balances[owner]; } /** * @dev See {IERC721-ownerOf}. */ function ownerOf( uint256 tokenId ) public view virtual override returns (address) { address owner = _ownersAndHashSeeds[tokenId].owner; require(owner != address(0), "ERC721: invalid token ID"); return owner; } /** * @dev See {IERC721Metadata-name}. */ function name() public view virtual override returns (string memory) { return _name; } /** * @dev See {IERC721Metadata-symbol}. */ function symbol() public view virtual override returns (string memory) { return _symbol; } /** * @dev See {IERC721Metadata-tokenURI}. */ function tokenURI( uint256 tokenId ) public view virtual override returns (string memory) { _requireMinted(tokenId); string memory baseURI = _baseURI(); return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : ""; } /** * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each * token will be the concatenation of the `baseURI` and the `tokenId`. Empty * by default, can be overridden in child contracts. */ function _baseURI() internal view virtual returns (string memory) { return ""; } /** * @dev See {IERC721-approve}. */ function approve(address to, uint256 tokenId) public virtual override { address owner = ERC721_PackedHashSeed.ownerOf(tokenId); require(to != owner, "ERC721: approval to current owner"); require( _msgSender() == owner || isApprovedForAll(owner, _msgSender()), "ERC721: approve caller is not token owner nor approved for all" ); _approve(to, tokenId); } /** * @dev See {IERC721-getApproved}. */ function getApproved( uint256 tokenId ) public view virtual override returns (address) { _requireMinted(tokenId); return _tokenApprovals[tokenId]; } /** * @dev See {IERC721-setApprovalForAll}. */ function setApprovalForAll( address operator, bool approved ) public virtual override { _setApprovalForAll(_msgSender(), operator, approved); } /** * @dev See {IERC721-isApprovedForAll}. */ function isApprovedForAll( address owner, address operator ) public view virtual override returns (bool) { return _operatorApprovals[owner][operator]; } /** * @dev See {IERC721-transferFrom}. */ function transferFrom( address from, address to, uint256 tokenId ) public virtual override { //solhint-disable-next-line max-line-length require( _isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner nor approved" ); _transfer(from, to, tokenId); } /** * @dev See {IERC721-safeTransferFrom}. */ function safeTransferFrom( address from, address to, uint256 tokenId ) public virtual override { safeTransferFrom(from, to, tokenId, ""); } /** * @dev See {IERC721-safeTransferFrom}. */ function safeTransferFrom( address from, address to, uint256 tokenId, bytes memory data ) public virtual override { require( _isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner nor approved" ); _safeTransfer(from, to, tokenId, data); } /** * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients * are aware of the ERC721 protocol to prevent tokens from being forever locked. * * `data` is additional data, it has no specified format and it is sent in call to `to`. * * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g. * implement alternative mechanisms to perform token transfer, such as signature-based. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must exist and be owned by `from`. * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. * * Emits a {Transfer} event. */ function _safeTransfer( address from, address to, uint256 tokenId, bytes memory data ) internal virtual { _transfer(from, to, tokenId); require( _checkOnERC721Received(from, to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer" ); } /** * @dev Returns whether `tokenId` exists. * * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}. * * Tokens start existing when they are minted (`_mint`), * and stop existing when they are burned (`_burn`). */ function _exists(uint256 tokenId) internal view virtual returns (bool) { return _ownersAndHashSeeds[tokenId].owner != address(0); } /** * @dev Returns whether `spender` is allowed to manage `tokenId`. * * Requirements: * * - `tokenId` must exist. */ function _isApprovedOrOwner( address spender, uint256 tokenId ) internal view virtual returns (bool) { address owner = ERC721_PackedHashSeed.ownerOf(tokenId); return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender); } /** * @dev Safely mints `tokenId` and transfers it to `to`. * * Requirements: * * - `tokenId` must not exist. * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. * * Emits a {Transfer} event. */ function _safeMint(address to, uint256 tokenId) internal virtual { _safeMint(to, tokenId, ""); } /** * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is * forwarded in {IERC721Receiver-onERC721Received} to contract recipients. */ function _safeMint( address to, uint256 tokenId, bytes memory data ) internal virtual { _mint(to, tokenId); require( _checkOnERC721Received(address(0), to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer" ); } /** * @dev Mints `tokenId` and transfers it to `to`. * * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible * * Requirements: * * - `tokenId` must not exist. * - `to` cannot be the zero address. * * Emits a {Transfer} event. */ function _mint(address to, uint256 tokenId) internal virtual { require(to != address(0), "ERC721: mint to the zero address"); require(!_exists(tokenId), "ERC721: token already minted"); _beforeTokenTransfer(address(0), to, tokenId); _balances[to] += 1; _ownersAndHashSeeds[tokenId].owner = to; emit Transfer(address(0), to, tokenId); _afterTokenTransfer(address(0), to, tokenId); } /** * @dev Destroys `tokenId`. * The approval is cleared when the token is burned. * * Requirements: * * - `tokenId` must exist. * * Emits a {Transfer} event. */ function _burn(uint256 tokenId) internal virtual { address owner = ERC721_PackedHashSeed.ownerOf(tokenId); _beforeTokenTransfer(owner, address(0), tokenId); // Clear approvals _approve(address(0), tokenId); _balances[owner] -= 1; delete _ownersAndHashSeeds[tokenId].owner; emit Transfer(owner, address(0), tokenId); _afterTokenTransfer(owner, address(0), tokenId); } /** * @dev Transfers `tokenId` from `from` to `to`. * As opposed to {transferFrom}, this imposes no restrictions on msg.sender. * * Requirements: * * - `to` cannot be the zero address. * - `tokenId` token must be owned by `from`. * * Emits a {Transfer} event. */ function _transfer( address from, address to, uint256 tokenId ) internal virtual { require( ERC721_PackedHashSeed.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner" ); require(to != address(0), "ERC721: transfer to the zero address"); _beforeTokenTransfer(from, to, tokenId); // Clear approvals from the previous owner _approve(address(0), tokenId); _balances[from] -= 1; _balances[to] += 1; _ownersAndHashSeeds[tokenId].owner = to; emit Transfer(from, to, tokenId); _afterTokenTransfer(from, to, tokenId); } /** * @dev Approve `to` to operate on `tokenId` * * Emits an {Approval} event. */ function _approve(address to, uint256 tokenId) internal virtual { _tokenApprovals[tokenId] = to; emit Approval(ERC721_PackedHashSeed.ownerOf(tokenId), to, tokenId); } /** * @dev Approve `operator` to operate on all of `owner` tokens * * Emits an {ApprovalForAll} event. */ function _setApprovalForAll( address owner, address operator, bool approved ) internal virtual { require(owner != operator, "ERC721: approve to caller"); _operatorApprovals[owner][operator] = approved; emit ApprovalForAll(owner, operator, approved); } /** * @dev Reverts if the `tokenId` has not been minted yet. */ function _requireMinted(uint256 tokenId) internal view virtual { require(_exists(tokenId), "ERC721: invalid token ID"); } /** * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address. * The call is not executed if the target address is not a contract. * * @param from address representing the previous owner of the given token ID * @param to target address that will receive the tokens * @param tokenId uint256 ID of the token to be transferred * @param data bytes optional data to send along with the call * @return bool whether the call correctly returned the expected magic value */ function _checkOnERC721Received( address from, address to, uint256 tokenId, bytes memory data ) private returns (bool) { if (to.isContract()) { try IERC721Receiver(to).onERC721Received( _msgSender(), from, tokenId, data ) returns (bytes4 retval) { return retval == IERC721Receiver.onERC721Received.selector; } catch (bytes memory reason) { if (reason.length == 0) { revert( "ERC721: transfer to non ERC721Receiver implementer" ); } else { /// @solidity memory-safe-assembly assembly { revert(add(32, reason), mload(reason)) } } } } else { return true; } } /** * @dev Hook that is called before any token transfer. This includes minting * and burning. * * Calling conditions: * * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be * transferred to `to`. * - When `from` is zero, `tokenId` will be minted for `to`. * - When `to` is zero, ``from``'s `tokenId` will be burned. * - `from` and `to` are never both zero. * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ function _beforeTokenTransfer( address from, address to, uint256 tokenId ) internal virtual {} /** * @dev Hook that is called after any transfer of tokens. This includes * minting and burning. * * Calling conditions: * * - when `from` and `to` are both non-zero. * - `from` and `to` are never both zero. * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ function _afterTokenTransfer( address from, address to, uint256 tokenId ) internal virtual {} }
File 3 of 5: GnosisSafeProxy
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title IProxy - Helper interface to access masterCopy of the Proxy on-chain /// @author Richard Meissner - <[email protected]> interface IProxy { function masterCopy() external view returns (address); } /// @title GnosisSafeProxy - Generic proxy contract allows to execute all transactions applying the code of a master contract. /// @author Stefan George - <[email protected]> /// @author Richard Meissner - <[email protected]> contract GnosisSafeProxy { // singleton always needs to be first declared variable, to ensure that it is at the same location in the contracts to which calls are delegated. // To reduce deployment costs this variable is internal and needs to be retrieved via `getStorageAt` address internal singleton; /// @dev Constructor function sets address of singleton contract. /// @param _singleton Singleton address. constructor(address _singleton) { require(_singleton != address(0), "Invalid singleton address provided"); singleton = _singleton; } /// @dev Fallback function forwards all transactions and returns all received return data. fallback() external payable { // solhint-disable-next-line no-inline-assembly assembly { let _singleton := and(sload(0), 0xffffffffffffffffffffffffffffffffffffffff) // 0xa619486e == keccak("masterCopy()"). The value is right padded to 32-bytes with 0s if eq(calldataload(0), 0xa619486e00000000000000000000000000000000000000000000000000000000) { mstore(0, _singleton) return(0, 0x20) } calldatacopy(0, 0, calldatasize()) let success := delegatecall(gas(), _singleton, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) if eq(success, 0) { revert(0, returndatasize()) } return(0, returndatasize()) } } } /// @title Proxy Factory - Allows to create new proxy contact and execute a message call to the new proxy within one transaction. /// @author Stefan George - <[email protected]> contract GnosisSafeProxyFactory { event ProxyCreation(GnosisSafeProxy proxy, address singleton); /// @dev Allows to create new proxy contact and execute a message call to the new proxy within one transaction. /// @param singleton Address of singleton contract. /// @param data Payload for message call sent to new proxy contract. function createProxy(address singleton, bytes memory data) public returns (GnosisSafeProxy proxy) { proxy = new GnosisSafeProxy(singleton); if (data.length > 0) // solhint-disable-next-line no-inline-assembly assembly { if eq(call(gas(), proxy, 0, add(data, 0x20), mload(data), 0, 0), 0) { revert(0, 0) } } emit ProxyCreation(proxy, singleton); } /// @dev Allows to retrieve the runtime code of a deployed Proxy. This can be used to check that the expected Proxy was deployed. function proxyRuntimeCode() public pure returns (bytes memory) { return type(GnosisSafeProxy).runtimeCode; } /// @dev Allows to retrieve the creation code used for the Proxy deployment. With this it is easily possible to calculate predicted address. function proxyCreationCode() public pure returns (bytes memory) { return type(GnosisSafeProxy).creationCode; } /// @dev Allows to create new proxy contact using CREATE2 but it doesn't run the initializer. /// This method is only meant as an utility to be called from other methods /// @param _singleton Address of singleton contract. /// @param initializer Payload for message call sent to new proxy contract. /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract. function deployProxyWithNonce( address _singleton, bytes memory initializer, uint256 saltNonce ) internal returns (GnosisSafeProxy proxy) { // If the initializer changes the proxy address should change too. Hashing the initializer data is cheaper than just concatinating it bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce)); bytes memory deploymentData = abi.encodePacked(type(GnosisSafeProxy).creationCode, uint256(uint160(_singleton))); // solhint-disable-next-line no-inline-assembly assembly { proxy := create2(0x0, add(0x20, deploymentData), mload(deploymentData), salt) } require(address(proxy) != address(0), "Create2 call failed"); } /// @dev Allows to create new proxy contact and execute a message call to the new proxy within one transaction. /// @param _singleton Address of singleton contract. /// @param initializer Payload for message call sent to new proxy contract. /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract. function createProxyWithNonce( address _singleton, bytes memory initializer, uint256 saltNonce ) public returns (GnosisSafeProxy proxy) { proxy = deployProxyWithNonce(_singleton, initializer, saltNonce); if (initializer.length > 0) // solhint-disable-next-line no-inline-assembly assembly { if eq(call(gas(), proxy, 0, add(initializer, 0x20), mload(initializer), 0, 0), 0) { revert(0, 0) } } emit ProxyCreation(proxy, _singleton); } /// @dev Allows to create new proxy contact, execute a message call to the new proxy and call a specified callback within one transaction /// @param _singleton Address of singleton contract. /// @param initializer Payload for message call sent to new proxy contract. /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract. /// @param callback Callback that will be invoced after the new proxy contract has been successfully deployed and initialized. function createProxyWithCallback( address _singleton, bytes memory initializer, uint256 saltNonce, IProxyCreationCallback callback ) public returns (GnosisSafeProxy proxy) { uint256 saltNonceWithCallback = uint256(keccak256(abi.encodePacked(saltNonce, callback))); proxy = createProxyWithNonce(_singleton, initializer, saltNonceWithCallback); if (address(callback) != address(0)) callback.proxyCreated(proxy, _singleton, initializer, saltNonce); } /// @dev Allows to get the address for a new proxy contact created via `createProxyWithNonce` /// This method is only meant for address calculation purpose when you use an initializer that would revert, /// therefore the response is returned with a revert. When calling this method set `from` to the address of the proxy factory. /// @param _singleton Address of singleton contract. /// @param initializer Payload for message call sent to new proxy contract. /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract. function calculateCreateProxyWithNonceAddress( address _singleton, bytes calldata initializer, uint256 saltNonce ) external returns (GnosisSafeProxy proxy) { proxy = deployProxyWithNonce(_singleton, initializer, saltNonce); revert(string(abi.encodePacked(proxy))); } } interface IProxyCreationCallback { function proxyCreated( GnosisSafeProxy proxy, address _singleton, bytes calldata initializer, uint256 saltNonce ) external; }
File 4 of 5: MinterFilterV1
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableMap.sol) pragma solidity ^0.8.0; import "./EnumerableSet.sol"; /** * @dev Library for managing an enumerable variant of Solidity's * https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`] * type. * * Maps have the following properties: * * - Entries are added, removed, and checked for existence in constant time * (O(1)). * - Entries are enumerated in O(n). No guarantees are made on the ordering. * * ``` * contract Example { * // Add the library methods * using EnumerableMap for EnumerableMap.UintToAddressMap; * * // Declare a set state variable * EnumerableMap.UintToAddressMap private myMap; * } * ``` * * As of v3.0.0, only maps of type `uint256 -> address` (`UintToAddressMap`) are * supported. */ library EnumerableMap { using EnumerableSet for EnumerableSet.Bytes32Set; // To implement this library for multiple types with as little code // repetition as possible, we write it in terms of a generic Map type with // bytes32 keys and values. // The Map implementation uses private functions, and user-facing // implementations (such as Uint256ToAddressMap) are just wrappers around // the underlying Map. // This means that we can only create new EnumerableMaps for types that fit // in bytes32. struct Map { // Storage of keys EnumerableSet.Bytes32Set _keys; mapping(bytes32 => bytes32) _values; } /** * @dev Adds a key-value pair to a map, or updates the value for an existing * key. O(1). * * Returns true if the key was added to the map, that is if it was not * already present. */ function _set( Map storage map, bytes32 key, bytes32 value ) private returns (bool) { map._values[key] = value; return map._keys.add(key); } /** * @dev Removes a key-value pair from a map. O(1). * * Returns true if the key was removed from the map, that is if it was present. */ function _remove(Map storage map, bytes32 key) private returns (bool) { delete map._values[key]; return map._keys.remove(key); } /** * @dev Returns true if the key is in the map. O(1). */ function _contains(Map storage map, bytes32 key) private view returns (bool) { return map._keys.contains(key); } /** * @dev Returns the number of key-value pairs in the map. O(1). */ function _length(Map storage map) private view returns (uint256) { return map._keys.length(); } /** * @dev Returns the key-value pair stored at position `index` in the map. O(1). * * Note that there are no guarantees on the ordering of entries inside the * array, and it may change when more entries are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function _at(Map storage map, uint256 index) private view returns (bytes32, bytes32) { bytes32 key = map._keys.at(index); return (key, map._values[key]); } /** * @dev Tries to returns the value associated with `key`. O(1). * Does not revert if `key` is not in the map. */ function _tryGet(Map storage map, bytes32 key) private view returns (bool, bytes32) { bytes32 value = map._values[key]; if (value == bytes32(0)) { return (_contains(map, key), bytes32(0)); } else { return (true, value); } } /** * @dev Returns the value associated with `key`. O(1). * * Requirements: * * - `key` must be in the map. */ function _get(Map storage map, bytes32 key) private view returns (bytes32) { bytes32 value = map._values[key]; require(value != 0 || _contains(map, key), "EnumerableMap: nonexistent key"); return value; } /** * @dev Same as {_get}, with a custom error message when `key` is not in the map. * * CAUTION: This function is deprecated because it requires allocating memory for the error * message unnecessarily. For custom revert reasons use {_tryGet}. */ function _get( Map storage map, bytes32 key, string memory errorMessage ) private view returns (bytes32) { bytes32 value = map._values[key]; require(value != 0 || _contains(map, key), errorMessage); return value; } // UintToAddressMap struct UintToAddressMap { Map _inner; } /** * @dev Adds a key-value pair to a map, or updates the value for an existing * key. O(1). * * Returns true if the key was added to the map, that is if it was not * already present. */ function set( UintToAddressMap storage map, uint256 key, address value ) internal returns (bool) { return _set(map._inner, bytes32(key), bytes32(uint256(uint160(value)))); } /** * @dev Removes a value from a set. O(1). * * Returns true if the key was removed from the map, that is if it was present. */ function remove(UintToAddressMap storage map, uint256 key) internal returns (bool) { return _remove(map._inner, bytes32(key)); } /** * @dev Returns true if the key is in the map. O(1). */ function contains(UintToAddressMap storage map, uint256 key) internal view returns (bool) { return _contains(map._inner, bytes32(key)); } /** * @dev Returns the number of elements in the map. O(1). */ function length(UintToAddressMap storage map) internal view returns (uint256) { return _length(map._inner); } /** * @dev Returns the element stored at position `index` in the set. O(1). * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function at(UintToAddressMap storage map, uint256 index) internal view returns (uint256, address) { (bytes32 key, bytes32 value) = _at(map._inner, index); return (uint256(key), address(uint160(uint256(value)))); } /** * @dev Tries to returns the value associated with `key`. O(1). * Does not revert if `key` is not in the map. * * _Available since v3.4._ */ function tryGet(UintToAddressMap storage map, uint256 key) internal view returns (bool, address) { (bool success, bytes32 value) = _tryGet(map._inner, bytes32(key)); return (success, address(uint160(uint256(value)))); } /** * @dev Returns the value associated with `key`. O(1). * * Requirements: * * - `key` must be in the map. */ function get(UintToAddressMap storage map, uint256 key) internal view returns (address) { return address(uint160(uint256(_get(map._inner, bytes32(key))))); } /** * @dev Same as {get}, with a custom error message when `key` is not in the map. * * CAUTION: This function is deprecated because it requires allocating memory for the error * message unnecessarily. For custom revert reasons use {tryGet}. */ function get( UintToAddressMap storage map, uint256 key, string memory errorMessage ) internal view returns (address) { return address(uint160(uint256(_get(map._inner, bytes32(key), errorMessage)))); } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableSet.sol) pragma solidity ^0.8.0; /** * @dev Library for managing * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive * types. * * Sets have the following properties: * * - Elements are added, removed, and checked for existence in constant time * (O(1)). * - Elements are enumerated in O(n). No guarantees are made on the ordering. * * ``` * contract Example { * // Add the library methods * using EnumerableSet for EnumerableSet.AddressSet; * * // Declare a set state variable * EnumerableSet.AddressSet private mySet; * } * ``` * * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`) * and `uint256` (`UintSet`) are supported. */ library EnumerableSet { // To implement this library for multiple types with as little code // repetition as possible, we write it in terms of a generic Set type with // bytes32 values. // The Set implementation uses private functions, and user-facing // implementations (such as AddressSet) are just wrappers around the // underlying Set. // This means that we can only create new EnumerableSets for types that fit // in bytes32. struct Set { // Storage of set values bytes32[] _values; // Position of the value in the `values` array, plus 1 because index 0 // means a value is not in the set. mapping(bytes32 => uint256) _indexes; } /** * @dev Add a value to a set. O(1). * * Returns true if the value was added to the set, that is if it was not * already present. */ function _add(Set storage set, bytes32 value) private returns (bool) { if (!_contains(set, value)) { set._values.push(value); // The value is stored at length-1, but we add 1 to all indexes // and use 0 as a sentinel value set._indexes[value] = set._values.length; return true; } else { return false; } } /** * @dev Removes a value from a set. O(1). * * Returns true if the value was removed from the set, that is if it was * present. */ function _remove(Set storage set, bytes32 value) private returns (bool) { // We read and store the value's index to prevent multiple reads from the same storage slot uint256 valueIndex = set._indexes[value]; if (valueIndex != 0) { // Equivalent to contains(set, value) // To delete an element from the _values array in O(1), we swap the element to delete with the last one in // the array, and then remove the last element (sometimes called as 'swap and pop'). // This modifies the order of the array, as noted in {at}. uint256 toDeleteIndex = valueIndex - 1; uint256 lastIndex = set._values.length - 1; if (lastIndex != toDeleteIndex) { bytes32 lastvalue = set._values[lastIndex]; // Move the last value to the index where the value to delete is set._values[toDeleteIndex] = lastvalue; // Update the index for the moved value set._indexes[lastvalue] = valueIndex; // Replace lastvalue's index to valueIndex } // Delete the slot where the moved value was stored set._values.pop(); // Delete the index for the deleted slot delete set._indexes[value]; return true; } else { return false; } } /** * @dev Returns true if the value is in the set. O(1). */ function _contains(Set storage set, bytes32 value) private view returns (bool) { return set._indexes[value] != 0; } /** * @dev Returns the number of values on the set. O(1). */ function _length(Set storage set) private view returns (uint256) { return set._values.length; } /** * @dev Returns the value stored at position `index` in the set. O(1). * * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function _at(Set storage set, uint256 index) private view returns (bytes32) { return set._values[index]; } /** * @dev Return the entire set in an array * * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that * this function has an unbounded cost, and using it as part of a state-changing function may render the function * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. */ function _values(Set storage set) private view returns (bytes32[] memory) { return set._values; } // Bytes32Set struct Bytes32Set { Set _inner; } /** * @dev Add a value to a set. O(1). * * Returns true if the value was added to the set, that is if it was not * already present. */ function add(Bytes32Set storage set, bytes32 value) internal returns (bool) { return _add(set._inner, value); } /** * @dev Removes a value from a set. O(1). * * Returns true if the value was removed from the set, that is if it was * present. */ function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) { return _remove(set._inner, value); } /** * @dev Returns true if the value is in the set. O(1). */ function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) { return _contains(set._inner, value); } /** * @dev Returns the number of values in the set. O(1). */ function length(Bytes32Set storage set) internal view returns (uint256) { return _length(set._inner); } /** * @dev Returns the value stored at position `index` in the set. O(1). * * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) { return _at(set._inner, index); } /** * @dev Return the entire set in an array * * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that * this function has an unbounded cost, and using it as part of a state-changing function may render the function * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. */ function values(Bytes32Set storage set) internal view returns (bytes32[] memory) { return _values(set._inner); } // AddressSet struct AddressSet { Set _inner; } /** * @dev Add a value to a set. O(1). * * Returns true if the value was added to the set, that is if it was not * already present. */ function add(AddressSet storage set, address value) internal returns (bool) { return _add(set._inner, bytes32(uint256(uint160(value)))); } /** * @dev Removes a value from a set. O(1). * * Returns true if the value was removed from the set, that is if it was * present. */ function remove(AddressSet storage set, address value) internal returns (bool) { return _remove(set._inner, bytes32(uint256(uint160(value)))); } /** * @dev Returns true if the value is in the set. O(1). */ function contains(AddressSet storage set, address value) internal view returns (bool) { return _contains(set._inner, bytes32(uint256(uint160(value)))); } /** * @dev Returns the number of values in the set. O(1). */ function length(AddressSet storage set) internal view returns (uint256) { return _length(set._inner); } /** * @dev Returns the value stored at position `index` in the set. O(1). * * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function at(AddressSet storage set, uint256 index) internal view returns (address) { return address(uint160(uint256(_at(set._inner, index)))); } /** * @dev Return the entire set in an array * * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that * this function has an unbounded cost, and using it as part of a state-changing function may render the function * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. */ function values(AddressSet storage set) internal view returns (address[] memory) { bytes32[] memory store = _values(set._inner); address[] memory result; assembly { result := store } return result; } // UintSet struct UintSet { Set _inner; } /** * @dev Add a value to a set. O(1). * * Returns true if the value was added to the set, that is if it was not * already present. */ function add(UintSet storage set, uint256 value) internal returns (bool) { return _add(set._inner, bytes32(value)); } /** * @dev Removes a value from a set. O(1). * * Returns true if the value was removed from the set, that is if it was * present. */ function remove(UintSet storage set, uint256 value) internal returns (bool) { return _remove(set._inner, bytes32(value)); } /** * @dev Returns true if the value is in the set. O(1). */ function contains(UintSet storage set, uint256 value) internal view returns (bool) { return _contains(set._inner, bytes32(value)); } /** * @dev Returns the number of values on the set. O(1). */ function length(UintSet storage set) internal view returns (uint256) { return _length(set._inner); } /** * @dev Returns the value stored at position `index` in the set. O(1). * * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function at(UintSet storage set, uint256 index) internal view returns (uint256) { return uint256(_at(set._inner, index)); } /** * @dev Return the entire set in an array * * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that * this function has an unbounded cost, and using it as part of a state-changing function may render the function * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. */ function values(UintSet storage set) internal view returns (uint256[] memory) { bytes32[] memory store = _values(set._inner); uint256[] memory result; assembly { result := store } return result; } } // SPDX-License-Identifier: LGPL-3.0-only // Created By: Art Blocks Inc. pragma solidity ^0.8.0; interface IAdminACLV0 { /** * @notice Token ID `_tokenId` minted to `_to`. * @param previousSuperAdmin The previous superAdmin address. * @param newSuperAdmin The new superAdmin address. * @param genArt721CoreAddressesToUpdate Array of genArt721Core * addresses to update to the new superAdmin, for indexing purposes only. */ event SuperAdminTransferred( address indexed previousSuperAdmin, address indexed newSuperAdmin, address[] genArt721CoreAddressesToUpdate ); /// Type of the Admin ACL contract, e.g. "AdminACLV0" function AdminACLType() external view returns (string memory); /// super admin address function superAdmin() external view returns (address); /** * @notice Calls transferOwnership on other contract from this contract. * This is useful for updating to a new AdminACL contract. * @dev this function should be gated to only superAdmin-like addresses. */ function transferOwnershipOn( address _contract, address _newAdminACL ) external; /** * @notice Calls renounceOwnership on other contract from this contract. * @dev this function should be gated to only superAdmin-like addresses. */ function renounceOwnershipOn(address _contract) external; /** * @notice Checks if sender `_sender` is allowed to call function with selector * `_selector` on contract `_contract`. */ function allowed( address _sender, address _contract, bytes4 _selector ) external returns (bool); } // SPDX-License-Identifier: LGPL-3.0-only // Created By: Art Blocks Inc. pragma solidity ^0.8.0; interface IFilteredMinterV0 { /** * @notice Price per token in wei updated for project `_projectId` to * `_pricePerTokenInWei`. */ event PricePerTokenInWeiUpdated( uint256 indexed _projectId, uint256 indexed _pricePerTokenInWei ); /** * @notice Currency updated for project `_projectId` to symbol * `_currencySymbol` and address `_currencyAddress`. */ event ProjectCurrencyInfoUpdated( uint256 indexed _projectId, address indexed _currencyAddress, string _currencySymbol ); /// togglePurchaseToDisabled updated event PurchaseToDisabledUpdated( uint256 indexed _projectId, bool _purchaseToDisabled ); // getter function of public variable function minterType() external view returns (string memory); function genArt721CoreAddress() external returns (address); function minterFilterAddress() external returns (address); // Triggers a purchase of a token from the desired project, to the // TX-sending address. function purchase( uint256 _projectId ) external payable returns (uint256 tokenId); // Triggers a purchase of a token from the desired project, to the specified // receiving address. function purchaseTo( address _to, uint256 _projectId ) external payable returns (uint256 tokenId); // Toggles the ability for `purchaseTo` to be called directly with a // specified receiving address that differs from the TX-sending address. function togglePurchaseToDisabled(uint256 _projectId) external; // Called to make the minter contract aware of the max invocations for a // given project. function setProjectMaxInvocations(uint256 _projectId) external; // Gets if token price is configured, token price in wei, currency symbol, // and currency address, assuming this is project's minter. // Supersedes any defined core price. function getPriceInfo( uint256 _projectId ) external view returns ( bool isConfigured, uint256 tokenPriceInWei, string memory currencySymbol, address currencyAddress ); } // SPDX-License-Identifier: LGPL-3.0-only // Created By: Art Blocks Inc. pragma solidity ^0.8.0; import "./IAdminACLV0.sol"; /// use the Royalty Registry's IManifold interface for token royalties import "./IManifold.sol"; /** * @title This interface is intended to house interface items that are common * across all GenArt721CoreContractV3 flagship and derivative implementations. * This interface extends the IManifold royalty interface in order to * add support the Royalty Registry by default. * @author Art Blocks Inc. */ interface IGenArt721CoreContractV3_Base is IManifold { /** * @notice Token ID `_tokenId` minted to `_to`. */ event Mint(address indexed _to, uint256 indexed _tokenId); /** * @notice currentMinter updated to `_currentMinter`. * @dev Implemented starting with V3 core */ event MinterUpdated(address indexed _currentMinter); /** * @notice Platform updated on bytes32-encoded field `_field`. */ event PlatformUpdated(bytes32 indexed _field); /** * @notice Project ID `_projectId` updated on bytes32-encoded field * `_update`. */ event ProjectUpdated(uint256 indexed _projectId, bytes32 indexed _update); event ProposedArtistAddressesAndSplits( uint256 indexed _projectId, address _artistAddress, address _additionalPayeePrimarySales, uint256 _additionalPayeePrimarySalesPercentage, address _additionalPayeeSecondarySales, uint256 _additionalPayeeSecondarySalesPercentage ); event AcceptedArtistAddressesAndSplits(uint256 indexed _projectId); // version and type of the core contract // coreVersion is a string of the form "0.x.y" function coreVersion() external view returns (string memory); // coreType is a string of the form "GenArt721CoreV3" function coreType() external view returns (string memory); // owner (pre-V3 was named admin) of contract // this is expected to be an Admin ACL contract for V3 function owner() external view returns (address); // Admin ACL contract for V3, will be at the address owner() function adminACLContract() external returns (IAdminACLV0); // backwards-compatible (pre-V3) admin - equal to owner() function admin() external view returns (address); /** * Function determining if _sender is allowed to call function with * selector _selector on contract `_contract`. Intended to be used with * peripheral contracts such as minters, as well as internally by the * core contract itself. */ function adminACLAllowed( address _sender, address _contract, bytes4 _selector ) external returns (bool); /// getter function of public variable function startingProjectId() external view returns (uint256); // getter function of public variable function nextProjectId() external view returns (uint256); // getter function of public mapping function tokenIdToProjectId( uint256 tokenId ) external view returns (uint256 projectId); // @dev this is not available in V0 function isMintWhitelisted(address minter) external view returns (bool); function projectIdToArtistAddress( uint256 _projectId ) external view returns (address payable); function projectIdToAdditionalPayeePrimarySales( uint256 _projectId ) external view returns (address payable); function projectIdToAdditionalPayeePrimarySalesPercentage( uint256 _projectId ) external view returns (uint256); function projectIdToSecondaryMarketRoyaltyPercentage( uint256 _projectId ) external view returns (uint256); function projectURIInfo( uint256 _projectId ) external view returns (string memory projectBaseURI); // @dev new function in V3 function projectStateData( uint256 _projectId ) external view returns ( uint256 invocations, uint256 maxInvocations, bool active, bool paused, uint256 completedTimestamp, bool locked ); function projectDetails( uint256 _projectId ) external view returns ( string memory projectName, string memory artist, string memory description, string memory website, string memory license ); function projectScriptDetails( uint256 _projectId ) external view returns ( string memory scriptTypeAndVersion, string memory aspectRatio, uint256 scriptCount ); function projectScriptByIndex( uint256 _projectId, uint256 _index ) external view returns (string memory); function tokenIdToHash(uint256 _tokenId) external view returns (bytes32); // function to set a token's hash (must be guarded) function setTokenHash_8PT(uint256 _tokenId, bytes32 _hash) external; // @dev gas-optimized signature in V3 for `mint` function mint_Ecf( address _to, uint256 _projectId, address _by ) external returns (uint256 tokenId); } // SPDX-License-Identifier: LGPL-3.0-only // Created By: Art Blocks Inc. pragma solidity ^0.8.0; import "./IAdminACLV0.sol"; import "./IGenArt721CoreContractV3_Base.sol"; /** * @title This interface extends IGenArt721CoreContractV3_Base with functions * that are part of the Art Blocks Flagship core contract. * @author Art Blocks Inc. */ // This interface extends IGenArt721CoreContractV3_Base with functions that are // in part of the Art Blocks Flagship core contract. interface IGenArt721CoreContractV3 is IGenArt721CoreContractV3_Base { // @dev new function in V3 function getPrimaryRevenueSplits( uint256 _projectId, uint256 _price ) external view returns ( uint256 artblocksRevenue_, address payable artblocksAddress_, uint256 artistRevenue_, address payable artistAddress_, uint256 additionalPayeePrimaryRevenue_, address payable additionalPayeePrimaryAddress_ ); // @dev Art Blocks primary sales payment address function artblocksPrimarySalesAddress() external view returns (address payable); /** * @notice Backwards-compatible (pre-V3) function returning Art Blocks * primary sales payment address (now called artblocksPrimarySalesAddress). */ function artblocksAddress() external view returns (address payable); // @dev Percentage of primary sales allocated to Art Blocks function artblocksPrimarySalesPercentage() external view returns (uint256); /** * @notice Backwards-compatible (pre-V3) function returning Art Blocks * primary sales percentage (now called artblocksPrimarySalesPercentage). */ function artblocksPercentage() external view returns (uint256); // @dev Art Blocks secondary sales royalties payment address function artblocksSecondarySalesAddress() external view returns (address payable); // @dev Basis points of secondary sales allocated to Art Blocks function artblocksSecondarySalesBPS() external view returns (uint256); /** * @notice Backwards-compatible (pre-V3) function that gets artist + * artist's additional payee royalty data for token ID `_tokenId`. * WARNING: Does not include Art Blocks portion of royalties. */ function getRoyaltyData( uint256 _tokenId ) external view returns ( address artistAddress, address additionalPayee, uint256 additionalPayeePercentage, uint256 royaltyFeeByID ); } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /// @dev Royalty Registry interface, used to support the Royalty Registry. /// @dev Source: https://github.com/manifoldxyz/royalty-registry-solidity/blob/main/contracts/specs/IManifold.sol /// @author: manifold.xyz /** * @dev Royalty interface for creator core classes */ interface IManifold { /** * @dev Get royalites of a token. Returns list of receivers and basisPoints * * bytes4(keccak256('getRoyalties(uint256)')) == 0xbb3bafd6 * * => 0xbb3bafd6 = 0xbb3bafd6 */ function getRoyalties( uint256 tokenId ) external view returns (address payable[] memory, uint256[] memory); } // SPDX-License-Identifier: LGPL-3.0-only // Created By: Art Blocks Inc. pragma solidity ^0.8.0; interface IMinterFilterV0 { /** * @notice Emitted when contract is deployed to notify indexing services * of the new contract deployment. */ event Deployed(); /** * @notice Approved minter `_minterAddress`. */ event MinterApproved(address indexed _minterAddress, string _minterType); /** * @notice Revoked approval for minter `_minterAddress` */ event MinterRevoked(address indexed _minterAddress); /** * @notice Minter `_minterAddress` of type `_minterType` * registered for project `_projectId`. */ event ProjectMinterRegistered( uint256 indexed _projectId, address indexed _minterAddress, string _minterType ); /** * @notice Any active minter removed for project `_projectId`. */ event ProjectMinterRemoved(uint256 indexed _projectId); function genArt721CoreAddress() external returns (address); function setMinterForProject(uint256, address) external; function removeMinterForProject(uint256) external; function mint( address _to, uint256 _projectId, address sender ) external returns (uint256); function getMinterForProject(uint256) external view returns (address); function projectHasMinter(uint256) external view returns (bool); } // SPDX-License-Identifier: LGPL-3.0-only // Created By: Art Blocks Inc. // Inspired by: https://ethereum.stackexchange.com/a/123950/103422 pragma solidity ^0.8.0; /** * @dev Operations on bytes32 data type, dealing with conversion to string. */ library Bytes32Strings { /** * @dev Intended to convert a `bytes32`-encoded string literal to `string`. * Trims zero padding to arrive at original string literal. */ function toString( bytes32 source ) internal pure returns (string memory result) { uint8 length = 0; while (source[length] != 0 && length < 32) { length++; } assembly { // free memory pointer result := mload(0x40) // update free memory pointer to new "memory end" // (offset is 64-bytes: 32 for length, 32 for data) mstore(0x40, add(result, 0x40)) // store length in first 32-byte memory slot mstore(result, length) // write actual data in second 32-byte memory slot mstore(add(result, 0x20), source) } } /** * @dev Intended to check if a `bytes32`-encoded string contains a given * character with UTF-8 character code `utf8CharCode exactly `targetQty` * times. Does not support searching for multi-byte characters, only * characters with UTF-8 character codes < 0x80. */ function containsExactCharacterQty( bytes32 source, uint8 utf8CharCode, uint8 targetQty ) internal pure returns (bool) { uint8 _occurrences = 0; uint8 i; for (i = 0; i < 32; ) { uint8 _charCode = uint8(source[i]); // if not a null byte, or a multi-byte UTF-8 character, check match if (_charCode != 0 && _charCode < 0x80) { if (_charCode == utf8CharCode) { unchecked { // no risk of overflow since max 32 iterations < max uin8=255 ++_occurrences; } } } unchecked { // no risk of overflow since max 32 iterations < max uin8=255 ++i; } } return _occurrences == targetQty; } } // SPDX-License-Identifier: LGPL-3.0-only // Created By: Art Blocks Inc. import "../../interfaces/v0.8.x/IMinterFilterV0.sol"; import "../../interfaces/v0.8.x/IFilteredMinterV0.sol"; import "../../interfaces/v0.8.x/IAdminACLV0.sol"; import "../../interfaces/v0.8.x/IGenArt721CoreContractV3.sol"; import "../../libs/v0.8.x/Bytes32Strings.sol"; import "@openzeppelin-4.5/contracts/utils/structs/EnumerableMap.sol"; pragma solidity 0.8.19; /** * @title Minter filter contract that allows filtered minters to be set * on a per-project basis. * This is designed to be used with IGenArt721CoreContractV3 contracts. * @author Art Blocks Inc. * @notice Privileged Roles and Ownership: * This contract is designed to be managed, with limited powers. * Privileged roles and abilities are controlled by the core contract's Admin * ACL contract and a project's artist. Both of these roles hold extensive * power and can modify a project's current minter. * Care must be taken to ensure that the admin ACL contract and artist * addresses are secure behind a multi-sig or other access control mechanism. * ---------------------------------------------------------------------------- * The following functions are restricted to the core contract's Admin ACL * contract: * - addApprovedMinter * - removeApprovedMinter * - removeMintersForProjects * ---------------------------------------------------------------------------- * The following functions are restricted to the core contract's Admin ACL * contract or a project's artist: * - setMinterForProject * - removeMinterForProject * ---------------------------------------------------------------------------- * Additional admin and artist privileged roles may be described on minters */ contract MinterFilterV1 is IMinterFilterV0 { // add Enumerable Map methods using EnumerableMap for EnumerableMap.UintToAddressMap; // add Bytes32Strings methods using Bytes32Strings for bytes32; /// version & type of this core contract bytes32 constant MINTER_FILTER_VERSION = "v1.0.1"; function minterFilterVersion() external pure returns (string memory) { return MINTER_FILTER_VERSION.toString(); } bytes32 constant MINTER_FILTER_TYPE = "MinterFilterV1"; function minterFilterType() external pure returns (string memory) { return MINTER_FILTER_TYPE.toString(); } /// Core contract address this minter interacts with address public immutable genArt721CoreAddress; /// This contract integrates with the IV3 core contract IGenArt721CoreContractV3 private immutable genArtCoreContract; /// projectId => minter address EnumerableMap.UintToAddressMap private minterForProject; /// minter address => qty projects currently using minter mapping(address => uint256) public numProjectsUsingMinter; /// minter address => is an approved minter? mapping(address => bool) public isApprovedMinter; function _onlyNonZeroAddress(address _address) internal pure { require(_address != address(0), "Must input non-zero address"); } // function to restrict access to only AdminACL allowed calls // @dev defers which ACL contract is used to the core contract function _onlyCoreAdminACL(bytes4 _selector) internal { require(_coreAdminACLAllowed(_selector), "Only Core AdminACL allowed"); } // function to restrict access to only the artist of `_projectId`, or // AdminACL allowed calls // @dev defers which ACL contract is used to the core contract function _onlyCoreAdminACLOrArtist( uint256 _projectId, bytes4 _selector ) internal { require( msg.sender == genArtCoreContract.projectIdToArtistAddress(_projectId) || _coreAdminACLAllowed(_selector), "Only Core AdminACL or Artist" ); } function _onlyValidProjectId(uint256 _projectId) internal view { require( _projectId < genArtCoreContract.nextProjectId(), "Only existing projects" ); } function _usingApprovedMinter(address _minterAddress) internal view { require( isApprovedMinter[_minterAddress], "Only approved minters are allowed" ); } /** * @notice Initializes contract to be a Minter for `_genArt721Address`. * @param _genArt721Address Art Blocks core contract address * this contract will be a minter for. Can never be updated. */ constructor(address _genArt721Address) { _onlyNonZeroAddress(_genArt721Address); genArt721CoreAddress = _genArt721Address; genArtCoreContract = IGenArt721CoreContractV3(_genArt721Address); emit Deployed(); } /** * @notice Internal function that determines if msg.sender is allowed to * call a function on this contract. Defers to core contract's * adminACLAllowed function. */ function _coreAdminACLAllowed(bytes4 _selector) internal returns (bool) { return genArtCoreContract.adminACLAllowed( msg.sender, address(this), _selector ); } /** * @notice Approves minter `_minterAddress`. * @param _minterAddress Minter to be added as an approved minter. */ function addApprovedMinter(address _minterAddress) external { _onlyCoreAdminACL(this.addApprovedMinter.selector); _onlyNonZeroAddress(_minterAddress); isApprovedMinter[_minterAddress] = true; emit MinterApproved( _minterAddress, IFilteredMinterV0(_minterAddress).minterType() ); } /** * @notice Removes previously approved minter `_minterAddress`. * @param _minterAddress Minter to remove. */ function removeApprovedMinter(address _minterAddress) external { _onlyCoreAdminACL(this.removeApprovedMinter.selector); require(isApprovedMinter[_minterAddress], "Only approved minters"); require( numProjectsUsingMinter[_minterAddress] == 0, "Only unused minters" ); isApprovedMinter[_minterAddress] = false; emit MinterRevoked(_minterAddress); } /** * @notice Sets minter for project `_projectId` to minter * `_minterAddress`. * @param _projectId Project ID to set minter for. * @param _minterAddress Minter to be the project's minter. */ function setMinterForProject( uint256 _projectId, address _minterAddress ) external { _onlyCoreAdminACLOrArtist( _projectId, this.setMinterForProject.selector ); _usingApprovedMinter(_minterAddress); _onlyValidProjectId(_projectId); // decrement number of projects using a previous minter (bool hasPreviousMinter, address previousMinter) = minterForProject .tryGet(_projectId); if (hasPreviousMinter) { numProjectsUsingMinter[previousMinter]--; } // add new minter numProjectsUsingMinter[_minterAddress]++; minterForProject.set(_projectId, _minterAddress); emit ProjectMinterRegistered( _projectId, _minterAddress, IFilteredMinterV0(_minterAddress).minterType() ); } /** * @notice Updates project `_projectId` to have no configured minter. * @param _projectId Project ID to remove minter. * @dev requires project to have an assigned minter */ function removeMinterForProject(uint256 _projectId) external { _onlyCoreAdminACLOrArtist( _projectId, this.removeMinterForProject.selector ); _removeMinterForProject(_projectId); } /** * @notice Updates an array of project IDs to have no configured minter. * @param _projectIds Array of project IDs to remove minters for. * @dev requires all project IDs to have an assigned minter * @dev caution with respect to single tx gas limits */ function removeMintersForProjects(uint256[] calldata _projectIds) external { _onlyCoreAdminACL(this.removeMintersForProjects.selector); uint256 numProjects = _projectIds.length; for (uint256 i; i < numProjects; i++) { _removeMinterForProject(_projectIds[i]); } } /** * @notice Updates project `_projectId` to have no configured minter * (reverts tx if project does not have an assigned minter). * @param _projectId Project ID to remove minter. */ function _removeMinterForProject(uint256 _projectId) private { // remove minter for project and emit // `minterForProject.get()` reverts tx if no minter set for project numProjectsUsingMinter[minterForProject.get(_projectId)]--; minterForProject.remove(_projectId); emit ProjectMinterRemoved(_projectId); } /** * @notice Mint a token from project `_projectId` to `_to`, originally * purchased by `sender`. * @param _to The new token's owner. * @param _projectId Project ID to mint a new token on. * @param sender Address purchasing a new token. * @return _tokenId Token ID of minted token * @dev reverts w/nonexistent key error when project has no assigned minter */ function mint( address _to, uint256 _projectId, address sender ) external returns (uint256 _tokenId) { // CHECKS // minter is the project's minter require( msg.sender == minterForProject.get(_projectId), "Only assigned minter" ); // EFFECTS uint256 tokenId = genArtCoreContract.mint_Ecf(_to, _projectId, sender); return tokenId; } /** * @notice Gets the assigned minter for project `_projectId`. * @param _projectId Project ID to query. * @return address Minter address assigned to project `_projectId` * @dev requires project to have an assigned minter */ function getMinterForProject( uint256 _projectId ) external view returns (address) { _onlyValidProjectId(_projectId); (bool _hasMinter, address _currentMinter) = minterForProject.tryGet( _projectId ); require(_hasMinter, "No minter assigned"); return _currentMinter; } /** * @notice Queries if project `_projectId` has an assigned minter. * @param _projectId Project ID to query. * @return bool true if project has an assigned minter, else false */ function projectHasMinter(uint256 _projectId) external view returns (bool) { _onlyValidProjectId(_projectId); (bool _hasMinter, ) = minterForProject.tryGet(_projectId); return _hasMinter; } /** * @notice Gets quantity of projects that have assigned minters. * @return uint256 quantity of projects that have assigned minters */ function getNumProjectsWithMinters() external view returns (uint256) { return minterForProject.length(); } /** * @notice Get project ID and minter address at index `_index` of * enumerable map. * @param _index enumerable map index to query. * @return projectId project ID at index `_index` * @return minterAddress minter address for project at index `_index` * @return minterType minter type of minter at minterAddress * @dev index must be < quantity of projects that have assigned minters */ function getProjectAndMinterInfoAt( uint256 _index ) external view returns ( uint256 projectId, address minterAddress, string memory minterType ) { (projectId, minterAddress) = minterForProject.at(_index); minterType = IFilteredMinterV0(minterAddress).minterType(); return (projectId, minterAddress, minterType); } }
File 5 of 5: GnosisSafe
// SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; import "./base/ModuleManager.sol"; import "./base/OwnerManager.sol"; import "./base/FallbackManager.sol"; import "./base/GuardManager.sol"; import "./common/EtherPaymentFallback.sol"; import "./common/Singleton.sol"; import "./common/SignatureDecoder.sol"; import "./common/SecuredTokenTransfer.sol"; import "./common/StorageAccessible.sol"; import "./interfaces/ISignatureValidator.sol"; import "./external/GnosisSafeMath.sol"; /// @title Gnosis Safe - A multisignature wallet with support for confirmations using signed messages based on ERC191. /// @author Stefan George - <[email protected]> /// @author Richard Meissner - <[email protected]> contract GnosisSafe is EtherPaymentFallback, Singleton, ModuleManager, OwnerManager, SignatureDecoder, SecuredTokenTransfer, ISignatureValidatorConstants, FallbackManager, StorageAccessible, GuardManager { using GnosisSafeMath for uint256; string public constant VERSION = "1.3.0"; // keccak256( // "EIP712Domain(uint256 chainId,address verifyingContract)" // ); bytes32 private constant DOMAIN_SEPARATOR_TYPEHASH = 0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218; // keccak256( // "SafeTx(address to,uint256 value,bytes data,uint8 operation,uint256 safeTxGas,uint256 baseGas,uint256 gasPrice,address gasToken,address refundReceiver,uint256 nonce)" // ); bytes32 private constant SAFE_TX_TYPEHASH = 0xbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d8; event SafeSetup(address indexed initiator, address[] owners, uint256 threshold, address initializer, address fallbackHandler); event ApproveHash(bytes32 indexed approvedHash, address indexed owner); event SignMsg(bytes32 indexed msgHash); event ExecutionFailure(bytes32 txHash, uint256 payment); event ExecutionSuccess(bytes32 txHash, uint256 payment); uint256 public nonce; bytes32 private _deprecatedDomainSeparator; // Mapping to keep track of all message hashes that have been approve by ALL REQUIRED owners mapping(bytes32 => uint256) public signedMessages; // Mapping to keep track of all hashes (message or transaction) that have been approve by ANY owners mapping(address => mapping(bytes32 => uint256)) public approvedHashes; // This constructor ensures that this contract can only be used as a master copy for Proxy contracts constructor() { // By setting the threshold it is not possible to call setup anymore, // so we create a Safe with 0 owners and threshold 1. // This is an unusable Safe, perfect for the singleton threshold = 1; } /// @dev Setup function sets initial storage of contract. /// @param _owners List of Safe owners. /// @param _threshold Number of required confirmations for a Safe transaction. /// @param to Contract address for optional delegate call. /// @param data Data payload for optional delegate call. /// @param fallbackHandler Handler for fallback calls to this contract /// @param paymentToken Token that should be used for the payment (0 is ETH) /// @param payment Value that should be paid /// @param paymentReceiver Adddress that should receive the payment (or 0 if tx.origin) function setup( address[] calldata _owners, uint256 _threshold, address to, bytes calldata data, address fallbackHandler, address paymentToken, uint256 payment, address payable paymentReceiver ) external { // setupOwners checks if the Threshold is already set, therefore preventing that this method is called twice setupOwners(_owners, _threshold); if (fallbackHandler != address(0)) internalSetFallbackHandler(fallbackHandler); // As setupOwners can only be called if the contract has not been initialized we don't need a check for setupModules setupModules(to, data); if (payment > 0) { // To avoid running into issues with EIP-170 we reuse the handlePayment function (to avoid adjusting code of that has been verified we do not adjust the method itself) // baseGas = 0, gasPrice = 1 and gas = payment => amount = (payment + 0) * 1 = payment handlePayment(payment, 0, 1, paymentToken, paymentReceiver); } emit SafeSetup(msg.sender, _owners, _threshold, to, fallbackHandler); } /// @dev Allows to execute a Safe transaction confirmed by required number of owners and then pays the account that submitted the transaction. /// Note: The fees are always transferred, even if the user transaction fails. /// @param to Destination address of Safe transaction. /// @param value Ether value of Safe transaction. /// @param data Data payload of Safe transaction. /// @param operation Operation type of Safe transaction. /// @param safeTxGas Gas that should be used for the Safe transaction. /// @param baseGas Gas costs that are independent of the transaction execution(e.g. base transaction fee, signature check, payment of the refund) /// @param gasPrice Gas price that should be used for the payment calculation. /// @param gasToken Token address (or 0 if ETH) that is used for the payment. /// @param refundReceiver Address of receiver of gas payment (or 0 if tx.origin). /// @param signatures Packed signature data ({bytes32 r}{bytes32 s}{uint8 v}) function execTransaction( address to, uint256 value, bytes calldata data, Enum.Operation operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address payable refundReceiver, bytes memory signatures ) public payable virtual returns (bool success) { bytes32 txHash; // Use scope here to limit variable lifetime and prevent `stack too deep` errors { bytes memory txHashData = encodeTransactionData( // Transaction info to, value, data, operation, safeTxGas, // Payment info baseGas, gasPrice, gasToken, refundReceiver, // Signature info nonce ); // Increase nonce and execute transaction. nonce++; txHash = keccak256(txHashData); checkSignatures(txHash, txHashData, signatures); } address guard = getGuard(); { if (guard != address(0)) { Guard(guard).checkTransaction( // Transaction info to, value, data, operation, safeTxGas, // Payment info baseGas, gasPrice, gasToken, refundReceiver, // Signature info signatures, msg.sender ); } } // We require some gas to emit the events (at least 2500) after the execution and some to perform code until the execution (500) // We also include the 1/64 in the check that is not send along with a call to counteract potential shortings because of EIP-150 require(gasleft() >= ((safeTxGas * 64) / 63).max(safeTxGas + 2500) + 500, "GS010"); // Use scope here to limit variable lifetime and prevent `stack too deep` errors { uint256 gasUsed = gasleft(); // If the gasPrice is 0 we assume that nearly all available gas can be used (it is always more than safeTxGas) // We only substract 2500 (compared to the 3000 before) to ensure that the amount passed is still higher than safeTxGas success = execute(to, value, data, operation, gasPrice == 0 ? (gasleft() - 2500) : safeTxGas); gasUsed = gasUsed.sub(gasleft()); // If no safeTxGas and no gasPrice was set (e.g. both are 0), then the internal tx is required to be successful // This makes it possible to use `estimateGas` without issues, as it searches for the minimum gas where the tx doesn't revert require(success || safeTxGas != 0 || gasPrice != 0, "GS013"); // We transfer the calculated tx costs to the tx.origin to avoid sending it to intermediate contracts that have made calls uint256 payment = 0; if (gasPrice > 0) { payment = handlePayment(gasUsed, baseGas, gasPrice, gasToken, refundReceiver); } if (success) emit ExecutionSuccess(txHash, payment); else emit ExecutionFailure(txHash, payment); } { if (guard != address(0)) { Guard(guard).checkAfterExecution(txHash, success); } } } function handlePayment( uint256 gasUsed, uint256 baseGas, uint256 gasPrice, address gasToken, address payable refundReceiver ) private returns (uint256 payment) { // solhint-disable-next-line avoid-tx-origin address payable receiver = refundReceiver == address(0) ? payable(tx.origin) : refundReceiver; if (gasToken == address(0)) { // For ETH we will only adjust the gas price to not be higher than the actual used gas price payment = gasUsed.add(baseGas).mul(gasPrice < tx.gasprice ? gasPrice : tx.gasprice); require(receiver.send(payment), "GS011"); } else { payment = gasUsed.add(baseGas).mul(gasPrice); require(transferToken(gasToken, receiver, payment), "GS012"); } } /** * @dev Checks whether the signature provided is valid for the provided data, hash. Will revert otherwise. * @param dataHash Hash of the data (could be either a message hash or transaction hash) * @param data That should be signed (this is passed to an external validator contract) * @param signatures Signature data that should be verified. Can be ECDSA signature, contract signature (EIP-1271) or approved hash. */ function checkSignatures( bytes32 dataHash, bytes memory data, bytes memory signatures ) public view { // Load threshold to avoid multiple storage loads uint256 _threshold = threshold; // Check that a threshold is set require(_threshold > 0, "GS001"); checkNSignatures(dataHash, data, signatures, _threshold); } /** * @dev Checks whether the signature provided is valid for the provided data, hash. Will revert otherwise. * @param dataHash Hash of the data (could be either a message hash or transaction hash) * @param data That should be signed (this is passed to an external validator contract) * @param signatures Signature data that should be verified. Can be ECDSA signature, contract signature (EIP-1271) or approved hash. * @param requiredSignatures Amount of required valid signatures. */ function checkNSignatures( bytes32 dataHash, bytes memory data, bytes memory signatures, uint256 requiredSignatures ) public view { // Check that the provided signature data is not too short require(signatures.length >= requiredSignatures.mul(65), "GS020"); // There cannot be an owner with address 0. address lastOwner = address(0); address currentOwner; uint8 v; bytes32 r; bytes32 s; uint256 i; for (i = 0; i < requiredSignatures; i++) { (v, r, s) = signatureSplit(signatures, i); if (v == 0) { // If v is 0 then it is a contract signature // When handling contract signatures the address of the contract is encoded into r currentOwner = address(uint160(uint256(r))); // Check that signature data pointer (s) is not pointing inside the static part of the signatures bytes // This check is not completely accurate, since it is possible that more signatures than the threshold are send. // Here we only check that the pointer is not pointing inside the part that is being processed require(uint256(s) >= requiredSignatures.mul(65), "GS021"); // Check that signature data pointer (s) is in bounds (points to the length of data -> 32 bytes) require(uint256(s).add(32) <= signatures.length, "GS022"); // Check if the contract signature is in bounds: start of data is s + 32 and end is start + signature length uint256 contractSignatureLen; // solhint-disable-next-line no-inline-assembly assembly { contractSignatureLen := mload(add(add(signatures, s), 0x20)) } require(uint256(s).add(32).add(contractSignatureLen) <= signatures.length, "GS023"); // Check signature bytes memory contractSignature; // solhint-disable-next-line no-inline-assembly assembly { // The signature data for contract signatures is appended to the concatenated signatures and the offset is stored in s contractSignature := add(add(signatures, s), 0x20) } require(ISignatureValidator(currentOwner).isValidSignature(data, contractSignature) == EIP1271_MAGIC_VALUE, "GS024"); } else if (v == 1) { // If v is 1 then it is an approved hash // When handling approved hashes the address of the approver is encoded into r currentOwner = address(uint160(uint256(r))); // Hashes are automatically approved by the sender of the message or when they have been pre-approved via a separate transaction require(msg.sender == currentOwner || approvedHashes[currentOwner][dataHash] != 0, "GS025"); } else if (v > 30) { // If v > 30 then default va (27,28) has been adjusted for eth_sign flow // To support eth_sign and similar we adjust v and hash the messageHash with the Ethereum message prefix before applying ecrecover currentOwner = ecrecover(keccak256(abi.encodePacked("\\x19Ethereum Signed Message:\ 32", dataHash)), v - 4, r, s); } else { // Default is the ecrecover flow with the provided data hash // Use ecrecover with the messageHash for EOA signatures currentOwner = ecrecover(dataHash, v, r, s); } require(currentOwner > lastOwner && owners[currentOwner] != address(0) && currentOwner != SENTINEL_OWNERS, "GS026"); lastOwner = currentOwner; } } /// @dev Allows to estimate a Safe transaction. /// This method is only meant for estimation purpose, therefore the call will always revert and encode the result in the revert data. /// Since the `estimateGas` function includes refunds, call this method to get an estimated of the costs that are deducted from the safe with `execTransaction` /// @param to Destination address of Safe transaction. /// @param value Ether value of Safe transaction. /// @param data Data payload of Safe transaction. /// @param operation Operation type of Safe transaction. /// @return Estimate without refunds and overhead fees (base transaction and payload data gas costs). /// @notice Deprecated in favor of common/StorageAccessible.sol and will be removed in next version. function requiredTxGas( address to, uint256 value, bytes calldata data, Enum.Operation operation ) external returns (uint256) { uint256 startGas = gasleft(); // We don't provide an error message here, as we use it to return the estimate require(execute(to, value, data, operation, gasleft())); uint256 requiredGas = startGas - gasleft(); // Convert response to string and return via error message revert(string(abi.encodePacked(requiredGas))); } /** * @dev Marks a hash as approved. This can be used to validate a hash that is used by a signature. * @param hashToApprove The hash that should be marked as approved for signatures that are verified by this contract. */ function approveHash(bytes32 hashToApprove) external { require(owners[msg.sender] != address(0), "GS030"); approvedHashes[msg.sender][hashToApprove] = 1; emit ApproveHash(hashToApprove, msg.sender); } /// @dev Returns the chain id used by this contract. function getChainId() public view returns (uint256) { uint256 id; // solhint-disable-next-line no-inline-assembly assembly { id := chainid() } return id; } function domainSeparator() public view returns (bytes32) { return keccak256(abi.encode(DOMAIN_SEPARATOR_TYPEHASH, getChainId(), this)); } /// @dev Returns the bytes that are hashed to be signed by owners. /// @param to Destination address. /// @param value Ether value. /// @param data Data payload. /// @param operation Operation type. /// @param safeTxGas Gas that should be used for the safe transaction. /// @param baseGas Gas costs for that are independent of the transaction execution(e.g. base transaction fee, signature check, payment of the refund) /// @param gasPrice Maximum gas price that should be used for this transaction. /// @param gasToken Token address (or 0 if ETH) that is used for the payment. /// @param refundReceiver Address of receiver of gas payment (or 0 if tx.origin). /// @param _nonce Transaction nonce. /// @return Transaction hash bytes. function encodeTransactionData( address to, uint256 value, bytes calldata data, Enum.Operation operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address refundReceiver, uint256 _nonce ) public view returns (bytes memory) { bytes32 safeTxHash = keccak256( abi.encode( SAFE_TX_TYPEHASH, to, value, keccak256(data), operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, _nonce ) ); return abi.encodePacked(bytes1(0x19), bytes1(0x01), domainSeparator(), safeTxHash); } /// @dev Returns hash to be signed by owners. /// @param to Destination address. /// @param value Ether value. /// @param data Data payload. /// @param operation Operation type. /// @param safeTxGas Fas that should be used for the safe transaction. /// @param baseGas Gas costs for data used to trigger the safe transaction. /// @param gasPrice Maximum gas price that should be used for this transaction. /// @param gasToken Token address (or 0 if ETH) that is used for the payment. /// @param refundReceiver Address of receiver of gas payment (or 0 if tx.origin). /// @param _nonce Transaction nonce. /// @return Transaction hash. function getTransactionHash( address to, uint256 value, bytes calldata data, Enum.Operation operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address refundReceiver, uint256 _nonce ) public view returns (bytes32) { return keccak256(encodeTransactionData(to, value, data, operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, _nonce)); } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; import "../common/Enum.sol"; /// @title Executor - A contract that can execute transactions /// @author Richard Meissner - <[email protected]> contract Executor { function execute( address to, uint256 value, bytes memory data, Enum.Operation operation, uint256 txGas ) internal returns (bool success) { if (operation == Enum.Operation.DelegateCall) { // solhint-disable-next-line no-inline-assembly assembly { success := delegatecall(txGas, to, add(data, 0x20), mload(data), 0, 0) } } else { // solhint-disable-next-line no-inline-assembly assembly { success := call(txGas, to, value, add(data, 0x20), mload(data), 0, 0) } } } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; import "../common/SelfAuthorized.sol"; /// @title Fallback Manager - A contract that manages fallback calls made to this contract /// @author Richard Meissner - <[email protected]> contract FallbackManager is SelfAuthorized { event ChangedFallbackHandler(address handler); // keccak256("fallback_manager.handler.address") bytes32 internal constant FALLBACK_HANDLER_STORAGE_SLOT = 0x6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d5; function internalSetFallbackHandler(address handler) internal { bytes32 slot = FALLBACK_HANDLER_STORAGE_SLOT; // solhint-disable-next-line no-inline-assembly assembly { sstore(slot, handler) } } /// @dev Allows to add a contract to handle fallback calls. /// Only fallback calls without value and with data will be forwarded. /// This can only be done via a Safe transaction. /// @param handler contract to handle fallbacks calls. function setFallbackHandler(address handler) public authorized { internalSetFallbackHandler(handler); emit ChangedFallbackHandler(handler); } // solhint-disable-next-line payable-fallback,no-complex-fallback fallback() external { bytes32 slot = FALLBACK_HANDLER_STORAGE_SLOT; // solhint-disable-next-line no-inline-assembly assembly { let handler := sload(slot) if iszero(handler) { return(0, 0) } calldatacopy(0, 0, calldatasize()) // The msg.sender address is shifted to the left by 12 bytes to remove the padding // Then the address without padding is stored right after the calldata mstore(calldatasize(), shl(96, caller())) // Add 20 bytes for the address appended add the end let success := call(gas(), handler, 0, 0, add(calldatasize(), 20), 0, 0) returndatacopy(0, 0, returndatasize()) if iszero(success) { revert(0, returndatasize()) } return(0, returndatasize()) } } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; import "../common/Enum.sol"; import "../common/SelfAuthorized.sol"; interface Guard { function checkTransaction( address to, uint256 value, bytes memory data, Enum.Operation operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address payable refundReceiver, bytes memory signatures, address msgSender ) external; function checkAfterExecution(bytes32 txHash, bool success) external; } /// @title Fallback Manager - A contract that manages fallback calls made to this contract /// @author Richard Meissner - <[email protected]> contract GuardManager is SelfAuthorized { event ChangedGuard(address guard); // keccak256("guard_manager.guard.address") bytes32 internal constant GUARD_STORAGE_SLOT = 0x4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8; /// @dev Set a guard that checks transactions before execution /// @param guard The address of the guard to be used or the 0 address to disable the guard function setGuard(address guard) external authorized { bytes32 slot = GUARD_STORAGE_SLOT; // solhint-disable-next-line no-inline-assembly assembly { sstore(slot, guard) } emit ChangedGuard(guard); } function getGuard() internal view returns (address guard) { bytes32 slot = GUARD_STORAGE_SLOT; // solhint-disable-next-line no-inline-assembly assembly { guard := sload(slot) } } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; import "../common/Enum.sol"; import "../common/SelfAuthorized.sol"; import "./Executor.sol"; /// @title Module Manager - A contract that manages modules that can execute transactions via this contract /// @author Stefan George - <[email protected]> /// @author Richard Meissner - <[email protected]> contract ModuleManager is SelfAuthorized, Executor { event EnabledModule(address module); event DisabledModule(address module); event ExecutionFromModuleSuccess(address indexed module); event ExecutionFromModuleFailure(address indexed module); address internal constant SENTINEL_MODULES = address(0x1); mapping(address => address) internal modules; function setupModules(address to, bytes memory data) internal { require(modules[SENTINEL_MODULES] == address(0), "GS100"); modules[SENTINEL_MODULES] = SENTINEL_MODULES; if (to != address(0)) // Setup has to complete successfully or transaction fails. require(execute(to, 0, data, Enum.Operation.DelegateCall, gasleft()), "GS000"); } /// @dev Allows to add a module to the whitelist. /// This can only be done via a Safe transaction. /// @notice Enables the module `module` for the Safe. /// @param module Module to be whitelisted. function enableModule(address module) public authorized { // Module address cannot be null or sentinel. require(module != address(0) && module != SENTINEL_MODULES, "GS101"); // Module cannot be added twice. require(modules[module] == address(0), "GS102"); modules[module] = modules[SENTINEL_MODULES]; modules[SENTINEL_MODULES] = module; emit EnabledModule(module); } /// @dev Allows to remove a module from the whitelist. /// This can only be done via a Safe transaction. /// @notice Disables the module `module` for the Safe. /// @param prevModule Module that pointed to the module to be removed in the linked list /// @param module Module to be removed. function disableModule(address prevModule, address module) public authorized { // Validate module address and check that it corresponds to module index. require(module != address(0) && module != SENTINEL_MODULES, "GS101"); require(modules[prevModule] == module, "GS103"); modules[prevModule] = modules[module]; modules[module] = address(0); emit DisabledModule(module); } /// @dev Allows a Module to execute a Safe transaction without any further confirmations. /// @param to Destination address of module transaction. /// @param value Ether value of module transaction. /// @param data Data payload of module transaction. /// @param operation Operation type of module transaction. function execTransactionFromModule( address to, uint256 value, bytes memory data, Enum.Operation operation ) public virtual returns (bool success) { // Only whitelisted modules are allowed. require(msg.sender != SENTINEL_MODULES && modules[msg.sender] != address(0), "GS104"); // Execute transaction without further confirmations. success = execute(to, value, data, operation, gasleft()); if (success) emit ExecutionFromModuleSuccess(msg.sender); else emit ExecutionFromModuleFailure(msg.sender); } /// @dev Allows a Module to execute a Safe transaction without any further confirmations and return data /// @param to Destination address of module transaction. /// @param value Ether value of module transaction. /// @param data Data payload of module transaction. /// @param operation Operation type of module transaction. function execTransactionFromModuleReturnData( address to, uint256 value, bytes memory data, Enum.Operation operation ) public returns (bool success, bytes memory returnData) { success = execTransactionFromModule(to, value, data, operation); // solhint-disable-next-line no-inline-assembly assembly { // Load free memory location let ptr := mload(0x40) // We allocate memory for the return data by setting the free memory location to // current free memory location + data size + 32 bytes for data size value mstore(0x40, add(ptr, add(returndatasize(), 0x20))) // Store the size mstore(ptr, returndatasize()) // Store the data returndatacopy(add(ptr, 0x20), 0, returndatasize()) // Point the return data to the correct memory location returnData := ptr } } /// @dev Returns if an module is enabled /// @return True if the module is enabled function isModuleEnabled(address module) public view returns (bool) { return SENTINEL_MODULES != module && modules[module] != address(0); } /// @dev Returns array of modules. /// @param start Start of the page. /// @param pageSize Maximum number of modules that should be returned. /// @return array Array of modules. /// @return next Start of the next page. function getModulesPaginated(address start, uint256 pageSize) external view returns (address[] memory array, address next) { // Init array with max page size array = new address[](pageSize); // Populate return array uint256 moduleCount = 0; address currentModule = modules[start]; while (currentModule != address(0x0) && currentModule != SENTINEL_MODULES && moduleCount < pageSize) { array[moduleCount] = currentModule; currentModule = modules[currentModule]; moduleCount++; } next = currentModule; // Set correct size of returned array // solhint-disable-next-line no-inline-assembly assembly { mstore(array, moduleCount) } } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; import "../common/SelfAuthorized.sol"; /// @title OwnerManager - Manages a set of owners and a threshold to perform actions. /// @author Stefan George - <[email protected]> /// @author Richard Meissner - <[email protected]> contract OwnerManager is SelfAuthorized { event AddedOwner(address owner); event RemovedOwner(address owner); event ChangedThreshold(uint256 threshold); address internal constant SENTINEL_OWNERS = address(0x1); mapping(address => address) internal owners; uint256 internal ownerCount; uint256 internal threshold; /// @dev Setup function sets initial storage of contract. /// @param _owners List of Safe owners. /// @param _threshold Number of required confirmations for a Safe transaction. function setupOwners(address[] memory _owners, uint256 _threshold) internal { // Threshold can only be 0 at initialization. // Check ensures that setup function can only be called once. require(threshold == 0, "GS200"); // Validate that threshold is smaller than number of added owners. require(_threshold <= _owners.length, "GS201"); // There has to be at least one Safe owner. require(_threshold >= 1, "GS202"); // Initializing Safe owners. address currentOwner = SENTINEL_OWNERS; for (uint256 i = 0; i < _owners.length; i++) { // Owner address cannot be null. address owner = _owners[i]; require(owner != address(0) && owner != SENTINEL_OWNERS && owner != address(this) && currentOwner != owner, "GS203"); // No duplicate owners allowed. require(owners[owner] == address(0), "GS204"); owners[currentOwner] = owner; currentOwner = owner; } owners[currentOwner] = SENTINEL_OWNERS; ownerCount = _owners.length; threshold = _threshold; } /// @dev Allows to add a new owner to the Safe and update the threshold at the same time. /// This can only be done via a Safe transaction. /// @notice Adds the owner `owner` to the Safe and updates the threshold to `_threshold`. /// @param owner New owner address. /// @param _threshold New threshold. function addOwnerWithThreshold(address owner, uint256 _threshold) public authorized { // Owner address cannot be null, the sentinel or the Safe itself. require(owner != address(0) && owner != SENTINEL_OWNERS && owner != address(this), "GS203"); // No duplicate owners allowed. require(owners[owner] == address(0), "GS204"); owners[owner] = owners[SENTINEL_OWNERS]; owners[SENTINEL_OWNERS] = owner; ownerCount++; emit AddedOwner(owner); // Change threshold if threshold was changed. if (threshold != _threshold) changeThreshold(_threshold); } /// @dev Allows to remove an owner from the Safe and update the threshold at the same time. /// This can only be done via a Safe transaction. /// @notice Removes the owner `owner` from the Safe and updates the threshold to `_threshold`. /// @param prevOwner Owner that pointed to the owner to be removed in the linked list /// @param owner Owner address to be removed. /// @param _threshold New threshold. function removeOwner( address prevOwner, address owner, uint256 _threshold ) public authorized { // Only allow to remove an owner, if threshold can still be reached. require(ownerCount - 1 >= _threshold, "GS201"); // Validate owner address and check that it corresponds to owner index. require(owner != address(0) && owner != SENTINEL_OWNERS, "GS203"); require(owners[prevOwner] == owner, "GS205"); owners[prevOwner] = owners[owner]; owners[owner] = address(0); ownerCount--; emit RemovedOwner(owner); // Change threshold if threshold was changed. if (threshold != _threshold) changeThreshold(_threshold); } /// @dev Allows to swap/replace an owner from the Safe with another address. /// This can only be done via a Safe transaction. /// @notice Replaces the owner `oldOwner` in the Safe with `newOwner`. /// @param prevOwner Owner that pointed to the owner to be replaced in the linked list /// @param oldOwner Owner address to be replaced. /// @param newOwner New owner address. function swapOwner( address prevOwner, address oldOwner, address newOwner ) public authorized { // Owner address cannot be null, the sentinel or the Safe itself. require(newOwner != address(0) && newOwner != SENTINEL_OWNERS && newOwner != address(this), "GS203"); // No duplicate owners allowed. require(owners[newOwner] == address(0), "GS204"); // Validate oldOwner address and check that it corresponds to owner index. require(oldOwner != address(0) && oldOwner != SENTINEL_OWNERS, "GS203"); require(owners[prevOwner] == oldOwner, "GS205"); owners[newOwner] = owners[oldOwner]; owners[prevOwner] = newOwner; owners[oldOwner] = address(0); emit RemovedOwner(oldOwner); emit AddedOwner(newOwner); } /// @dev Allows to update the number of required confirmations by Safe owners. /// This can only be done via a Safe transaction. /// @notice Changes the threshold of the Safe to `_threshold`. /// @param _threshold New threshold. function changeThreshold(uint256 _threshold) public authorized { // Validate that threshold is smaller than number of owners. require(_threshold <= ownerCount, "GS201"); // There has to be at least one Safe owner. require(_threshold >= 1, "GS202"); threshold = _threshold; emit ChangedThreshold(threshold); } function getThreshold() public view returns (uint256) { return threshold; } function isOwner(address owner) public view returns (bool) { return owner != SENTINEL_OWNERS && owners[owner] != address(0); } /// @dev Returns array of owners. /// @return Array of Safe owners. function getOwners() public view returns (address[] memory) { address[] memory array = new address[](ownerCount); // populate return array uint256 index = 0; address currentOwner = owners[SENTINEL_OWNERS]; while (currentOwner != SENTINEL_OWNERS) { array[index] = currentOwner; currentOwner = owners[currentOwner]; index++; } return array; } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title Enum - Collection of enums /// @author Richard Meissner - <[email protected]> contract Enum { enum Operation {Call, DelegateCall} } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title EtherPaymentFallback - A contract that has a fallback to accept ether payments /// @author Richard Meissner - <[email protected]> contract EtherPaymentFallback { event SafeReceived(address indexed sender, uint256 value); /// @dev Fallback function accepts Ether transactions. receive() external payable { emit SafeReceived(msg.sender, msg.value); } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title SecuredTokenTransfer - Secure token transfer /// @author Richard Meissner - <[email protected]> contract SecuredTokenTransfer { /// @dev Transfers a token and returns if it was a success /// @param token Token that should be transferred /// @param receiver Receiver to whom the token should be transferred /// @param amount The amount of tokens that should be transferred function transferToken( address token, address receiver, uint256 amount ) internal returns (bool transferred) { // 0xa9059cbb - keccack("transfer(address,uint256)") bytes memory data = abi.encodeWithSelector(0xa9059cbb, receiver, amount); // solhint-disable-next-line no-inline-assembly assembly { // We write the return value to scratch space. // See https://docs.soliditylang.org/en/v0.7.6/internals/layout_in_memory.html#layout-in-memory let success := call(sub(gas(), 10000), token, 0, add(data, 0x20), mload(data), 0, 0x20) switch returndatasize() case 0 { transferred := success } case 0x20 { transferred := iszero(or(iszero(success), iszero(mload(0)))) } default { transferred := 0 } } } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title SelfAuthorized - authorizes current contract to perform actions /// @author Richard Meissner - <[email protected]> contract SelfAuthorized { function requireSelfCall() private view { require(msg.sender == address(this), "GS031"); } modifier authorized() { // This is a function call as it minimized the bytecode size requireSelfCall(); _; } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title SignatureDecoder - Decodes signatures that a encoded as bytes /// @author Richard Meissner - <[email protected]> contract SignatureDecoder { /// @dev divides bytes signature into `uint8 v, bytes32 r, bytes32 s`. /// @notice Make sure to peform a bounds check for @param pos, to avoid out of bounds access on @param signatures /// @param pos which signature to read. A prior bounds check of this parameter should be performed, to avoid out of bounds access /// @param signatures concatenated rsv signatures function signatureSplit(bytes memory signatures, uint256 pos) internal pure returns ( uint8 v, bytes32 r, bytes32 s ) { // The signature format is a compact form of: // {bytes32 r}{bytes32 s}{uint8 v} // Compact means, uint8 is not padded to 32 bytes. // solhint-disable-next-line no-inline-assembly assembly { let signaturePos := mul(0x41, pos) r := mload(add(signatures, add(signaturePos, 0x20))) s := mload(add(signatures, add(signaturePos, 0x40))) // Here we are loading the last 32 bytes, including 31 bytes // of 's'. There is no 'mload8' to do this. // // 'byte' is not working due to the Solidity parser, so lets // use the second best option, 'and' v := and(mload(add(signatures, add(signaturePos, 0x41))), 0xff) } } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title Singleton - Base for singleton contracts (should always be first super contract) /// This contract is tightly coupled to our proxy contract (see `proxies/GnosisSafeProxy.sol`) /// @author Richard Meissner - <[email protected]> contract Singleton { // singleton always needs to be first declared variable, to ensure that it is at the same location as in the Proxy contract. // It should also always be ensured that the address is stored alone (uses a full word) address private singleton; } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /// @title StorageAccessible - generic base contract that allows callers to access all internal storage. /// @notice See https://github.com/gnosis/util-contracts/blob/bb5fe5fb5df6d8400998094fb1b32a178a47c3a1/contracts/StorageAccessible.sol contract StorageAccessible { /** * @dev Reads `length` bytes of storage in the currents contract * @param offset - the offset in the current contract's storage in words to start reading from * @param length - the number of words (32 bytes) of data to read * @return the bytes that were read. */ function getStorageAt(uint256 offset, uint256 length) public view returns (bytes memory) { bytes memory result = new bytes(length * 32); for (uint256 index = 0; index < length; index++) { // solhint-disable-next-line no-inline-assembly assembly { let word := sload(add(offset, index)) mstore(add(add(result, 0x20), mul(index, 0x20)), word) } } return result; } /** * @dev Performs a delegetecall on a targetContract in the context of self. * Internally reverts execution to avoid side effects (making it static). * * This method reverts with data equal to `abi.encode(bool(success), bytes(response))`. * Specifically, the `returndata` after a call to this method will be: * `success:bool || response.length:uint256 || response:bytes`. * * @param targetContract Address of the contract containing the code to execute. * @param calldataPayload Calldata that should be sent to the target contract (encoded method name and arguments). */ function simulateAndRevert(address targetContract, bytes memory calldataPayload) external { // solhint-disable-next-line no-inline-assembly assembly { let success := delegatecall(gas(), targetContract, add(calldataPayload, 0x20), mload(calldataPayload), 0, 0) mstore(0x00, success) mstore(0x20, returndatasize()) returndatacopy(0x40, 0, returndatasize()) revert(0, add(returndatasize(), 0x40)) } } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; /** * @title GnosisSafeMath * @dev Math operations with safety checks that revert on error * Renamed from SafeMath to GnosisSafeMath to avoid conflicts * TODO: remove once open zeppelin update to solc 0.5.0 */ library GnosisSafeMath { /** * @dev Multiplies two numbers, reverts on overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256) { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 if (a == 0) { return 0; } uint256 c = a * b; require(c / a == b); return c; } /** * @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend). */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { require(b <= a); uint256 c = a - b; return c; } /** * @dev Adds two numbers, reverts on overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a); return c; } /** * @dev Returns the largest of two numbers. */ function max(uint256 a, uint256 b) internal pure returns (uint256) { return a >= b ? a : b; } } // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.7.0 <0.9.0; contract ISignatureValidatorConstants { // bytes4(keccak256("isValidSignature(bytes,bytes)") bytes4 internal constant EIP1271_MAGIC_VALUE = 0x20c13b0b; } abstract contract ISignatureValidator is ISignatureValidatorConstants { /** * @dev Should return whether the signature provided is valid for the provided data * @param _data Arbitrary length data signed on the behalf of address(this) * @param _signature Signature byte array associated with _data * * MUST return the bytes4 magic value 0x20c13b0b when function passes. * MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5) * MUST allow external calls */ function isValidSignature(bytes memory _data, bytes memory _signature) public view virtual returns (bytes4); }