@artblocks/contracts 0.0.1 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -11
- package/artifacts/contracts/AdminACLV1.sol/AdminACLV1.json +2 -2
- package/artifacts/contracts/BasicPolyptychRandomizerV0.sol/BasicPolyptychRandomizerV0.json +229 -0
- package/artifacts/contracts/BasicRandomizerV2.sol/BasicRandomizerV2.json +3 -3
- package/artifacts/contracts/GenArt721CoreV3.sol/GenArt721CoreV3.json +2 -2
- package/artifacts/contracts/GenArt721CoreV3_Engine.sol/GenArt721CoreV3_Engine.json +2 -2
- package/artifacts/contracts/archive/GenArt721CoreV0.sol/Address.json +10 -0
- package/artifacts/contracts/archive/GenArt721CoreV0.sol/Counters.json +10 -0
- package/artifacts/contracts/archive/GenArt721CoreV0.sol/CustomERC721Metadata.json +425 -0
- package/artifacts/contracts/archive/GenArt721CoreV0.sol/ERC165.json +38 -0
- package/artifacts/contracts/archive/GenArt721CoreV0.sol/ERC721.json +322 -0
- package/artifacts/contracts/archive/GenArt721CoreV0.sol/ERC721Enumerable.json +384 -0
- package/artifacts/contracts/archive/GenArt721CoreV0.sol/ERC721ReceiverMock.json +101 -0
- package/artifacts/contracts/archive/GenArt721CoreV0.sol/GenArt721.json +1528 -0
- package/artifacts/contracts/archive/GenArt721CoreV0.sol/IERC165.json +32 -0
- package/artifacts/contracts/archive/GenArt721CoreV0.sol/IERC721.json +316 -0
- package/artifacts/contracts/archive/GenArt721CoreV0.sol/IERC721Enumerable.json +378 -0
- package/artifacts/contracts/archive/GenArt721CoreV0.sol/IERC721Receiver.json +47 -0
- package/artifacts/contracts/archive/GenArt721CoreV0.sol/SafeMath.json +10 -0
- package/artifacts/contracts/archive/GenArt721CoreV0.sol/Strings.json +10 -0
- package/artifacts/contracts/archive/GenArt721CoreV1.sol/Address.json +10 -0
- package/artifacts/contracts/archive/GenArt721CoreV1.sol/Counters.json +10 -0
- package/artifacts/contracts/archive/GenArt721CoreV1.sol/CustomERC721Metadata.json +425 -0
- package/artifacts/contracts/archive/GenArt721CoreV1.sol/ERC165.json +38 -0
- package/artifacts/contracts/archive/GenArt721CoreV1.sol/ERC721.json +322 -0
- package/artifacts/contracts/archive/GenArt721CoreV1.sol/ERC721Enumerable.json +384 -0
- package/artifacts/contracts/archive/GenArt721CoreV1.sol/GenArt721Core.json +1773 -0
- package/artifacts/contracts/archive/GenArt721CoreV1.sol/IERC165.json +32 -0
- package/artifacts/contracts/archive/GenArt721CoreV1.sol/IERC721.json +316 -0
- package/artifacts/contracts/archive/GenArt721CoreV1.sol/IERC721Enumerable.json +378 -0
- package/artifacts/contracts/archive/GenArt721CoreV1.sol/IERC721Receiver.json +47 -0
- package/artifacts/contracts/archive/GenArt721CoreV1.sol/Randomizer.json +26 -0
- package/artifacts/contracts/archive/GenArt721CoreV1.sol/SafeMath.json +10 -0
- package/artifacts/contracts/archive/GenArt721CoreV1.sol/Strings.json +10 -0
- package/artifacts/contracts/archive/minter-suite/Minters/MinterDAExp/MinterDAExpV0.sol/MinterDAExpV0.json +575 -0
- package/artifacts/contracts/archive/minter-suite/Minters/MinterDAExp/MinterDAExpV1.sol/MinterDAExpV1.json +463 -0
- package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDAExp/MinterDAExpV2.sol/MinterDAExpV2.json +3 -3
- package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDAExp/MinterDAExpV3.sol/MinterDAExpV3.json +3 -3
- package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDAExpSettlement/MinterDAExpSettlementV0.sol/MinterDAExpSettlementV0.json +15 -15
- package/artifacts/contracts/archive/minter-suite/Minters/MinterDAExpSettlement/MinterDAExpSettlementV1.sol/MinterDAExpSettlementV1.json +1066 -0
- package/artifacts/contracts/archive/minter-suite/Minters/MinterDALin/MinterDALinV0.sol/MinterDALinV0.json +551 -0
- package/artifacts/contracts/archive/minter-suite/Minters/MinterDALin/MinterDALinV1.sol/MinterDALinV1.json +439 -0
- package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDALin/MinterDALinV2.sol/MinterDALinV2.json +3 -3
- package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDALin/MinterDALinV3.sol/MinterDALinV3.json +3 -3
- package/artifacts/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV0.sol/MinterHolderV0.json +622 -0
- package/artifacts/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV1.sol/MinterHolderV1.json +719 -0
- package/artifacts/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV2.sol/MinterHolderV2.json +794 -0
- package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterHolder/MinterHolderV3.sol/MinterHolderV3.json +3 -3
- package/artifacts/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV0.sol/MinterMerkleV0.json +873 -0
- package/artifacts/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV1.sol/MinterMerkleV1.json +932 -0
- package/artifacts/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV2.sol/MinterMerkleV2.json +984 -0
- package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterMerkle/MinterMerkleV3.sol/MinterMerkleV3.json +3 -3
- package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterMerkle/MinterMerkleV4.sol/MinterMerkleV4.json +3 -3
- package/artifacts/contracts/archive/minter-suite/Minters/MinterSetPrice/MinterSetPriceV0.sol/MinterSetPriceV0.json +400 -0
- package/artifacts/contracts/{minter-suite/Minters/MinterSetPrice/deprecated → archive/minter-suite/Minters/MinterSetPrice}/MinterSetPriceV1.sol/MinterSetPriceV1.json +3 -3
- package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPrice/MinterSetPriceV2.sol/MinterSetPriceV2.json +3 -3
- package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPrice/MinterSetPriceV3.sol/MinterSetPriceV3.json +3 -3
- package/artifacts/contracts/{minter-suite/Minters/MinterSetPriceERC20/deprecated → archive/minter-suite/Minters/MinterSetPriceERC20}/MinterSetPriceERC20V0.sol/MinterSetPriceERC20V0.json +3 -3
- package/artifacts/contracts/archive/minter-suite/Minters/MinterSetPriceERC20/MinterSetPriceERC20V1.sol/MinterSetPriceERC20V1.json +349 -0
- package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPriceERC20/MinterSetPriceERC20V2.sol/MinterSetPriceERC20V2.json +3 -3
- package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPriceERC20/MinterSetPriceERC20V3.sol/MinterSetPriceERC20V3.json +3 -3
- package/artifacts/contracts/explorations/GenArt721CoreV3_Explorations.sol/GenArt721CoreV3_Explorations.json +2 -2
- package/artifacts/contracts/interfaces/0.8.x/IFilteredMinterDAExpSettlementV0.sol/IFilteredMinterDAExpSettlementV0.json +12 -12
- package/artifacts/contracts/interfaces/0.8.x/IFilteredMinterDAExpSettlementV1.sol/IFilteredMinterDAExpSettlementV1.json +767 -0
- package/artifacts/contracts/interfaces/0.8.x/IFilteredMinterDAExpSettlement_Mixin.sol/IFilteredMinterDAExpSettlement_Mixin.json +161 -0
- package/artifacts/contracts/interfaces/0.8.x/IMinterBaseV0.sol/IMinterBaseV0.json +24 -0
- package/artifacts/contracts/interfaces/0.8.x/IMinterFilterV0.sol/IMinterFilterV0.json +6 -0
- package/artifacts/contracts/interfaces/0.8.x/IMintingAPICoreV0.sol/IMintingAPICoreV0.json +185 -0
- package/artifacts/contracts/interfaces/0.8.x/IMintingAPIMinterV0.sol/IMintingAPIMinterV0.json +88 -0
- package/artifacts/contracts/interfaces/0.8.x/IRandomizerPolyptychV0.sol/IRandomizerPolyptychV0.json +87 -0
- package/artifacts/contracts/interfaces/0.8.x/IRandomizerV2.sol/IRandomizerV2.json +1 -1
- package/artifacts/contracts/minter-suite/MinterFilter/MinterFilterV0.sol/MinterFilterV0.json +8 -2
- package/artifacts/contracts/minter-suite/MinterFilter/MinterFilterV1.sol/MinterFilterV1.json +34 -2
- package/artifacts/contracts/minter-suite/Minters/MinterBase_v0_1_1.sol/MinterBase.json +24 -0
- package/artifacts/contracts/minter-suite/Minters/MinterDAExpSettlementV2.sol/MinterDAExpSettlementV2.json +1127 -0
- package/artifacts/contracts/minter-suite/Minters/MinterDAExpV4.sol/MinterDAExpV4.json +882 -0
- package/artifacts/contracts/minter-suite/Minters/MinterDALinV4.sol/MinterDALinV4.json +858 -0
- package/artifacts/contracts/minter-suite/Minters/MinterHolderV4.sol/MinterHolderV4.json +1126 -0
- package/artifacts/contracts/minter-suite/Minters/MinterMerkleV5.sol/MinterMerkleV5.json +1065 -0
- package/artifacts/contracts/minter-suite/Minters/MinterPolyptychV0.sol/IGenArt721CoreContractV3WithRandomizer.json +662 -0
- package/artifacts/contracts/minter-suite/Minters/MinterPolyptychV0.sol/MinterPolyptychV0.json +1268 -0
- package/artifacts/contracts/minter-suite/Minters/MinterSetPriceERC20V4.sol/MinterSetPriceERC20V4.json +768 -0
- package/artifacts/contracts/minter-suite/Minters/MinterSetPriceV4.sol/MinterSetPriceV4.json +697 -0
- package/artifacts/contracts/mock/GenArt721CoreV3_Engine_IncorrectCoreType.sol/GenArt721CoreV3_Engine_IncorrectCoreType.json +1817 -0
- package/artifacts/contracts/mock/RandomizerV2_NoAssignMock.sol/RandomizerV2_NoAssignMock.json +3 -3
- package/contracts/BasicPolyptychRandomizerV0.sol +126 -0
- package/contracts/BasicRandomizerV2.sol +3 -3
- package/contracts/archive/GenArt721CoreV0.sol +1621 -0
- package/contracts/archive/GenArt721CoreV1.sol +1577 -0
- package/contracts/archive/minter-suite/Minters/MinterDAExp/MinterDAExpV0.sol +503 -0
- package/contracts/archive/minter-suite/Minters/MinterDAExp/MinterDAExpV1.sol +442 -0
- package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDAExp/MinterDAExpV2.sol +3 -3
- package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDAExp/MinterDAExpV3.sol +3 -3
- package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDAExpSettlement/MinterDAExpSettlementV0.sol +3 -3
- package/contracts/archive/minter-suite/Minters/MinterDAExpSettlement/MinterDAExpSettlementV1.sol +1026 -0
- package/contracts/archive/minter-suite/Minters/MinterDALin/MinterDALinV0.sol +450 -0
- package/contracts/archive/minter-suite/Minters/MinterDALin/MinterDALinV1.sol +406 -0
- package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDALin/MinterDALinV2.sol +3 -3
- package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDALin/MinterDALinV3.sol +3 -3
- package/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV0.sol +540 -0
- package/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV1.sol +648 -0
- package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterHolder/MinterHolderV2.sol +6 -6
- package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterHolder/MinterHolderV3.sol +4 -4
- package/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV0.sol +523 -0
- package/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV1.sol +602 -0
- package/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV2.sol +679 -0
- package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterMerkle/MinterMerkleV3.sol +4 -4
- package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterMerkle/MinterMerkleV4.sol +4 -4
- package/contracts/archive/minter-suite/Minters/MinterSetPrice/MinterSetPriceV0.sol +333 -0
- package/contracts/{minter-suite/Minters/MinterSetPrice/deprecated → archive/minter-suite/Minters/MinterSetPrice}/MinterSetPriceV1.sol +0 -0
- package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPrice/MinterSetPriceV2.sol +3 -3
- package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPrice/MinterSetPriceV3.sol +3 -3
- package/contracts/{minter-suite/Minters/MinterSetPriceERC20/deprecated → archive/minter-suite/Minters/MinterSetPriceERC20}/MinterSetPriceERC20V0.sol +0 -0
- package/contracts/archive/minter-suite/Minters/MinterSetPriceERC20/MinterSetPriceERC20V1.sol +411 -0
- package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPriceERC20/MinterSetPriceERC20V2.sol +3 -3
- package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPriceERC20/MinterSetPriceERC20V3.sol +3 -3
- package/contracts/interfaces/0.8.x/IFilteredMinterDAExpSettlementV0.sol +4 -53
- package/contracts/interfaces/0.8.x/IFilteredMinterDAExpSettlementV1.sol +21 -0
- package/contracts/interfaces/0.8.x/IFilteredMinterDAExpSettlement_Mixin.sol +62 -0
- package/contracts/interfaces/0.8.x/IMinterBaseV0.sol +20 -0
- package/contracts/interfaces/0.8.x/IMinterFilterV0.sol +6 -0
- package/contracts/interfaces/0.8.x/IMintingAPICoreV0.sol +62 -0
- package/contracts/interfaces/0.8.x/IMintingAPIMinterV0.sol +31 -0
- package/contracts/interfaces/0.8.x/IRandomizerPolyptychV0.sol +31 -0
- package/contracts/interfaces/0.8.x/IRandomizerV2.sol +5 -2
- package/contracts/minter-suite/MinterFilter/MinterFilterV1.sol +18 -0
- package/contracts/minter-suite/Minters/MinterBase_v0_1_1.sol +293 -0
- package/contracts/minter-suite/Minters/MinterDAExpSettlementV2.sol +1312 -0
- package/contracts/minter-suite/Minters/MinterDAExpV4.sol +600 -0
- package/contracts/minter-suite/Minters/MinterDALinV4.sol +552 -0
- package/contracts/minter-suite/Minters/MinterHolderV4.sol +781 -0
- package/contracts/minter-suite/Minters/MinterMerkleV5.sol +717 -0
- package/contracts/minter-suite/Minters/MinterPolyptychV0.sol +998 -0
- package/contracts/minter-suite/Minters/MinterSetPriceERC20V4.sol +480 -0
- package/contracts/minter-suite/Minters/MinterSetPriceV4.sol +380 -0
- package/contracts/mock/GenArt721CoreV3_Engine_IncorrectCoreType.sol +1979 -0
- package/package.json +8 -2
- package/artifacts/contracts/minter-suite/Minters/MinterHolder/MinterHolderV2.sol/MinterHolderV2.json +0 -794
|
@@ -0,0 +1,1979 @@
|
|
|
1
|
+
// SPDX-License-Identifier: LGPL-3.0-only
|
|
2
|
+
pragma solidity 0.8.17;
|
|
3
|
+
|
|
4
|
+
// Created By: Art Blocks Inc.
|
|
5
|
+
|
|
6
|
+
import "../interfaces/0.8.x/IRandomizerV2.sol";
|
|
7
|
+
import "../interfaces/0.8.x/IAdminACLV0.sol";
|
|
8
|
+
import "../interfaces/0.8.x/IEngineRegistryV0.sol";
|
|
9
|
+
import "../interfaces/0.8.x/IGenArt721CoreContractV3_Engine.sol";
|
|
10
|
+
import "../interfaces/0.8.x/IDependencyRegistryCompatibleV0.sol";
|
|
11
|
+
import "../interfaces/0.8.x/IManifold.sol";
|
|
12
|
+
|
|
13
|
+
import "@openzeppelin-4.7/contracts/utils/Strings.sol";
|
|
14
|
+
import "@openzeppelin-4.7/contracts/access/Ownable.sol";
|
|
15
|
+
import "../libs/0.8.x/ERC721_PackedHashSeed.sol";
|
|
16
|
+
import "../libs/0.8.x/BytecodeStorage.sol";
|
|
17
|
+
import "../libs/0.8.x/Bytes32Strings.sol";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @title MISCONFIGURED Art Blocks Engine ERC-721 core contract, V3.
|
|
21
|
+
* @author Art Blocks Inc.
|
|
22
|
+
* @notice DO NOT DEPLOY!
|
|
23
|
+
* @dev This contract is a mock contract that is used to test the
|
|
24
|
+
* integration of minters that integrate with either flagship or engine contracts
|
|
25
|
+
*/
|
|
26
|
+
contract GenArt721CoreV3_Engine_IncorrectCoreType is
|
|
27
|
+
ERC721_PackedHashSeed,
|
|
28
|
+
Ownable,
|
|
29
|
+
IDependencyRegistryCompatibleV0,
|
|
30
|
+
IManifold
|
|
31
|
+
// INTERFACE CONFORMANCE INTENTIONALLY OMITTED TO ENABLE MOCKING BUGGED CONTRACT
|
|
32
|
+
{
|
|
33
|
+
using BytecodeStorage for string;
|
|
34
|
+
using BytecodeStorage for address;
|
|
35
|
+
using Bytes32Strings for bytes32;
|
|
36
|
+
using Strings for uint256;
|
|
37
|
+
using Strings for address;
|
|
38
|
+
uint256 constant ONE_HUNDRED = 100;
|
|
39
|
+
uint256 constant ONE_MILLION = 1_000_000;
|
|
40
|
+
uint24 constant ONE_MILLION_UINT24 = 1_000_000;
|
|
41
|
+
uint256 constant FOUR_WEEKS_IN_SECONDS = 2_419_200;
|
|
42
|
+
uint8 constant AT_CHARACTER_CODE = uint8(bytes1("@")); // 0x40
|
|
43
|
+
|
|
44
|
+
// numeric constants
|
|
45
|
+
uint256 constant MAX_PROVIDER_SECONDARY_SALES_BPS = 10000; // 10_000 BPS = 100%
|
|
46
|
+
uint256 constant ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE = 95; // 95%
|
|
47
|
+
|
|
48
|
+
// This contract emits generic events that contain fields that indicate
|
|
49
|
+
// which parameter has been updated. This is sufficient for application
|
|
50
|
+
// state management, while also simplifying the contract and indexing code.
|
|
51
|
+
// This was done as an alternative to having custom events that emit what
|
|
52
|
+
// field-values have changed for each event, given that changed values can
|
|
53
|
+
// be introspected by indexers due to the design of this smart contract
|
|
54
|
+
// exposing these state changes via publicly viewable fields.
|
|
55
|
+
//
|
|
56
|
+
// The following fields are used to indicate which contract-level parameter
|
|
57
|
+
// has been updated in the `PlatformUpdated` event:
|
|
58
|
+
bytes32 constant FIELD_NEXT_PROJECT_ID = "nextProjectId";
|
|
59
|
+
bytes32 constant FIELD_NEW_PROJECTS_FORBIDDEN = "newProjectsForbidden";
|
|
60
|
+
bytes32 constant FIELD_DEFAULT_BASE_URI = "defaultBaseURI";
|
|
61
|
+
bytes32 constant FIELD_RANDOMIZER_ADDRESS = "randomizerAddress";
|
|
62
|
+
bytes32 constant FIELD_ARTBLOCKS_DEPENDENCY_REGISTRY_ADDRESS =
|
|
63
|
+
"dependencyRegistryAddress";
|
|
64
|
+
bytes32 constant FIELD_PROVIDER_SALES_ADDRESSES = "providerSalesAddresses";
|
|
65
|
+
bytes32 constant FIELD_PROVIDER_PRIMARY_SALES_PERCENTAGES =
|
|
66
|
+
"providerPrimaryPercentages";
|
|
67
|
+
bytes32 constant FIELD_PROVIDER_SECONDARY_SALES_BPS =
|
|
68
|
+
"providerSecondaryBPS";
|
|
69
|
+
// The following fields are used to indicate which project-level parameter
|
|
70
|
+
// has been updated in the `ProjectUpdated` event:
|
|
71
|
+
bytes32 constant FIELD_PROJECT_COMPLETED = "completed";
|
|
72
|
+
bytes32 constant FIELD_PROJECT_ACTIVE = "active";
|
|
73
|
+
bytes32 constant FIELD_PROJECT_ARTIST_ADDRESS = "artistAddress";
|
|
74
|
+
bytes32 constant FIELD_PROJECT_PAUSED = "paused";
|
|
75
|
+
bytes32 constant FIELD_PROJECT_CREATED = "created";
|
|
76
|
+
bytes32 constant FIELD_PROJECT_NAME = "name";
|
|
77
|
+
bytes32 constant FIELD_PROJECT_ARTIST_NAME = "artistName";
|
|
78
|
+
bytes32 constant FIELD_PROJECT_SECONDARY_MARKET_ROYALTY_PERCENTAGE =
|
|
79
|
+
"royaltyPercentage";
|
|
80
|
+
bytes32 constant FIELD_PROJECT_DESCRIPTION = "description";
|
|
81
|
+
bytes32 constant FIELD_PROJECT_WEBSITE = "website";
|
|
82
|
+
bytes32 constant FIELD_PROJECT_LICENSE = "license";
|
|
83
|
+
bytes32 constant FIELD_PROJECT_MAX_INVOCATIONS = "maxInvocations";
|
|
84
|
+
bytes32 constant FIELD_PROJECT_SCRIPT = "script";
|
|
85
|
+
bytes32 constant FIELD_PROJECT_SCRIPT_TYPE = "scriptType";
|
|
86
|
+
bytes32 constant FIELD_PROJECT_ASPECT_RATIO = "aspectRatio";
|
|
87
|
+
bytes32 constant FIELD_PROJECT_BASE_URI = "baseURI";
|
|
88
|
+
|
|
89
|
+
/// Dependency registry managed by Art Blocks
|
|
90
|
+
address public artblocksDependencyRegistryAddress;
|
|
91
|
+
|
|
92
|
+
/// current randomizer contract
|
|
93
|
+
IRandomizerV2 public randomizerContract;
|
|
94
|
+
|
|
95
|
+
/// append-only array of all randomizer contract addresses ever used by
|
|
96
|
+
/// this contract
|
|
97
|
+
address[] private _historicalRandomizerAddresses;
|
|
98
|
+
|
|
99
|
+
/// admin ACL contract
|
|
100
|
+
IAdminACLV0 public adminACLContract;
|
|
101
|
+
|
|
102
|
+
struct Project {
|
|
103
|
+
uint24 invocations;
|
|
104
|
+
uint24 maxInvocations;
|
|
105
|
+
uint24 scriptCount;
|
|
106
|
+
// max uint64 ~= 1.8e19 sec ~= 570 billion years
|
|
107
|
+
uint64 completedTimestamp;
|
|
108
|
+
bool active;
|
|
109
|
+
bool paused;
|
|
110
|
+
string name;
|
|
111
|
+
string artist;
|
|
112
|
+
string description;
|
|
113
|
+
string website;
|
|
114
|
+
string license;
|
|
115
|
+
string projectBaseURI;
|
|
116
|
+
bytes32 scriptTypeAndVersion;
|
|
117
|
+
string aspectRatio;
|
|
118
|
+
// mapping from script index to address storing script in bytecode
|
|
119
|
+
mapping(uint256 => address) scriptBytecodeAddresses;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
mapping(uint256 => Project) projects;
|
|
123
|
+
|
|
124
|
+
/// packed struct containing project financial information
|
|
125
|
+
struct ProjectFinance {
|
|
126
|
+
address payable additionalPayeePrimarySales;
|
|
127
|
+
// packed uint: max of 95, max uint8 = 255
|
|
128
|
+
uint8 secondaryMarketRoyaltyPercentage;
|
|
129
|
+
address payable additionalPayeeSecondarySales;
|
|
130
|
+
// packed uint: max of 100, max uint8 = 255
|
|
131
|
+
uint8 additionalPayeeSecondarySalesPercentage;
|
|
132
|
+
address payable artistAddress;
|
|
133
|
+
// packed uint: max of 100, max uint8 = 255
|
|
134
|
+
uint8 additionalPayeePrimarySalesPercentage;
|
|
135
|
+
}
|
|
136
|
+
// Project financials mapping
|
|
137
|
+
mapping(uint256 => ProjectFinance) projectIdToFinancials;
|
|
138
|
+
|
|
139
|
+
/// hash of artist's proposed payment updates to be approved by admin
|
|
140
|
+
mapping(uint256 => bytes32) public proposedArtistAddressesAndSplitsHash;
|
|
141
|
+
|
|
142
|
+
/// The render provider payment address for all primary sales revenues
|
|
143
|
+
/// (packed)
|
|
144
|
+
address payable public renderProviderPrimarySalesAddress;
|
|
145
|
+
/// Percentage of primary sales revenue allocated to the render provider
|
|
146
|
+
/// (packed)
|
|
147
|
+
// packed uint: max of 100, max uint8 = 255
|
|
148
|
+
uint8 private _renderProviderPrimarySalesPercentage = 10;
|
|
149
|
+
/// The platform provider payment address for all primary sales revenues
|
|
150
|
+
/// (packed)
|
|
151
|
+
address payable public platformProviderPrimarySalesAddress;
|
|
152
|
+
/// Percentage of primary sales revenue allocated to the platform provider
|
|
153
|
+
/// (packed)
|
|
154
|
+
// packed uint: max of 100, max uint8 = 255
|
|
155
|
+
uint8 private _platformProviderPrimarySalesPercentage = 10;
|
|
156
|
+
|
|
157
|
+
/// The render provider payment address for all secondary sales royalty
|
|
158
|
+
/// revenues
|
|
159
|
+
address payable public renderProviderSecondarySalesAddress;
|
|
160
|
+
/// Basis Points of secondary sales royalties allocated to the
|
|
161
|
+
/// render provider
|
|
162
|
+
uint256 public renderProviderSecondarySalesBPS = 250;
|
|
163
|
+
/// The platform provider payment address for all secondary sales royalty
|
|
164
|
+
/// revenues
|
|
165
|
+
address payable public platformProviderSecondarySalesAddress;
|
|
166
|
+
/// Basis Points of secondary sales royalties allocated to the
|
|
167
|
+
/// platform provider
|
|
168
|
+
uint256 public platformProviderSecondarySalesBPS = 250;
|
|
169
|
+
|
|
170
|
+
/// single minter allowed for this core contract
|
|
171
|
+
address public minterContract;
|
|
172
|
+
|
|
173
|
+
/// starting (initial) project ID on this contract
|
|
174
|
+
uint256 public immutable startingProjectId;
|
|
175
|
+
|
|
176
|
+
/// next project ID to be created
|
|
177
|
+
uint248 private _nextProjectId;
|
|
178
|
+
|
|
179
|
+
/// bool indicating if adding new projects is forbidden;
|
|
180
|
+
/// default behavior is to allow new projects
|
|
181
|
+
bool public newProjectsForbidden;
|
|
182
|
+
|
|
183
|
+
/// configuration variable (determined at time of deployment)
|
|
184
|
+
/// that determines whether or not admin approval^ should be required
|
|
185
|
+
/// to accept artist address change proposals, or if these proposals
|
|
186
|
+
/// should always auto-approve, as determined by the business process
|
|
187
|
+
/// requirements of the Engine partner using this contract.
|
|
188
|
+
///
|
|
189
|
+
/// ^does not apply in the case where contract-ownership itself is revoked
|
|
190
|
+
bool public immutable autoApproveArtistSplitProposals;
|
|
191
|
+
|
|
192
|
+
/// version & type of this core contract
|
|
193
|
+
bytes32 constant CORE_VERSION = "v3.1.1";
|
|
194
|
+
|
|
195
|
+
function coreVersion() external pure returns (string memory) {
|
|
196
|
+
return CORE_VERSION.toString();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// THE FOLLOWING IS INTENTIONALLY MISCONFIGURED TO REFLECT THIS IS A
|
|
200
|
+
// FLAGSHIP CONTRACT, NOT AN ENGINE CONTRACT
|
|
201
|
+
// @dev this is for testing purposes only, and helps to prove that a
|
|
202
|
+
// minter will not skip payment addresses if this value is misconfigured
|
|
203
|
+
// on an engine core contract.
|
|
204
|
+
bytes32 constant CORE_TYPE = "GenArt721CoreV3";
|
|
205
|
+
|
|
206
|
+
// should be:
|
|
207
|
+
// bytes32 constant CORE_TYPE = "GenArt721CoreV3_Engine";
|
|
208
|
+
|
|
209
|
+
function coreType() external pure returns (string memory) {
|
|
210
|
+
return CORE_TYPE.toString();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/// default base URI to initialize all new project projectBaseURI values to
|
|
214
|
+
string public defaultBaseURI;
|
|
215
|
+
|
|
216
|
+
modifier onlyNonZeroAddress(address _address) {
|
|
217
|
+
require(_address != address(0), "Must input non-zero address");
|
|
218
|
+
_;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
modifier onlyNonEmptyString(string memory _string) {
|
|
222
|
+
require(bytes(_string).length != 0, "Must input non-empty string");
|
|
223
|
+
_;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
modifier onlyValidTokenId(uint256 _tokenId) {
|
|
227
|
+
require(_exists(_tokenId), "Token ID does not exist");
|
|
228
|
+
_;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
modifier onlyValidProjectId(uint256 _projectId) {
|
|
232
|
+
require(
|
|
233
|
+
(_projectId >= startingProjectId) && (_projectId < _nextProjectId),
|
|
234
|
+
"Project ID does not exist"
|
|
235
|
+
);
|
|
236
|
+
_;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
modifier onlyUnlocked(uint256 _projectId) {
|
|
240
|
+
// Note: calling `_projectUnlocked` enforces that the `_projectId`
|
|
241
|
+
// passed in is valid.`
|
|
242
|
+
require(_projectUnlocked(_projectId), "Only if unlocked");
|
|
243
|
+
_;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
modifier onlyAdminACL(bytes4 _selector) {
|
|
247
|
+
require(
|
|
248
|
+
adminACLAllowed(msg.sender, address(this), _selector),
|
|
249
|
+
"Only Admin ACL allowed"
|
|
250
|
+
);
|
|
251
|
+
_;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
modifier onlyArtist(uint256 _projectId) {
|
|
255
|
+
require(
|
|
256
|
+
msg.sender == projectIdToFinancials[_projectId].artistAddress,
|
|
257
|
+
"Only artist"
|
|
258
|
+
);
|
|
259
|
+
_;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
modifier onlyArtistOrAdminACL(uint256 _projectId, bytes4 _selector) {
|
|
263
|
+
require(
|
|
264
|
+
msg.sender == projectIdToFinancials[_projectId].artistAddress ||
|
|
265
|
+
adminACLAllowed(msg.sender, address(this), _selector),
|
|
266
|
+
"Only artist or Admin ACL allowed"
|
|
267
|
+
);
|
|
268
|
+
_;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* This modifier allows the artist of a project to call a function if the
|
|
273
|
+
* owner of the contract has renounced ownership. This is to allow the
|
|
274
|
+
* contract to continue to function if the owner decides to renounce
|
|
275
|
+
* ownership.
|
|
276
|
+
*/
|
|
277
|
+
modifier onlyAdminACLOrRenouncedArtist(
|
|
278
|
+
uint256 _projectId,
|
|
279
|
+
bytes4 _selector
|
|
280
|
+
) {
|
|
281
|
+
require(
|
|
282
|
+
adminACLAllowed(msg.sender, address(this), _selector) ||
|
|
283
|
+
(owner() == address(0) &&
|
|
284
|
+
msg.sender ==
|
|
285
|
+
projectIdToFinancials[_projectId].artistAddress),
|
|
286
|
+
"Only Admin ACL allowed, or artist if owner has renounced"
|
|
287
|
+
);
|
|
288
|
+
_;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* @notice Initializes contract.
|
|
293
|
+
* @param _tokenName Name of token.
|
|
294
|
+
* @param _tokenSymbol Token symbol.
|
|
295
|
+
* @param _randomizerContract Randomizer contract.
|
|
296
|
+
* @param _adminACLContract Address of admin access control contract, to be
|
|
297
|
+
* set as contract owner.
|
|
298
|
+
* @param _startingProjectId The initial next project ID.
|
|
299
|
+
* @param _autoApproveArtistSplitProposals Whether or not to always
|
|
300
|
+
* auto-approve proposed artist split updates.
|
|
301
|
+
* @dev _startingProjectId should be set to a value much, much less than
|
|
302
|
+
* max(uint248), but an explicit input type of `uint248` is used as it is
|
|
303
|
+
* safer to cast up to `uint256` than it is to cast down for the purposes
|
|
304
|
+
* of setting `_nextProjectId`.
|
|
305
|
+
*/
|
|
306
|
+
constructor(
|
|
307
|
+
string memory _tokenName,
|
|
308
|
+
string memory _tokenSymbol,
|
|
309
|
+
address _renderProviderAddress,
|
|
310
|
+
address _platformProviderAddress,
|
|
311
|
+
address _randomizerContract,
|
|
312
|
+
address _adminACLContract,
|
|
313
|
+
uint248 _startingProjectId,
|
|
314
|
+
bool _autoApproveArtistSplitProposals,
|
|
315
|
+
address _engineRegistryContract
|
|
316
|
+
)
|
|
317
|
+
ERC721_PackedHashSeed(_tokenName, _tokenSymbol)
|
|
318
|
+
onlyNonZeroAddress(_renderProviderAddress)
|
|
319
|
+
onlyNonZeroAddress(_platformProviderAddress)
|
|
320
|
+
onlyNonZeroAddress(_randomizerContract)
|
|
321
|
+
onlyNonZeroAddress(_adminACLContract)
|
|
322
|
+
{
|
|
323
|
+
// setup immutable `autoApproveArtistSplitProposals` config
|
|
324
|
+
autoApproveArtistSplitProposals = _autoApproveArtistSplitProposals;
|
|
325
|
+
// record contracts starting project ID
|
|
326
|
+
// casting-up is safe
|
|
327
|
+
startingProjectId = uint256(_startingProjectId);
|
|
328
|
+
_updateProviderSalesAddresses(
|
|
329
|
+
_renderProviderAddress,
|
|
330
|
+
_renderProviderAddress,
|
|
331
|
+
_platformProviderAddress,
|
|
332
|
+
_platformProviderAddress
|
|
333
|
+
);
|
|
334
|
+
_updateRandomizerAddress(_randomizerContract);
|
|
335
|
+
// set AdminACL management contract as owner
|
|
336
|
+
_transferOwnership(_adminACLContract);
|
|
337
|
+
// initialize default base URI
|
|
338
|
+
_updateDefaultBaseURI(
|
|
339
|
+
string.concat(
|
|
340
|
+
"https://token.artblocks.io/",
|
|
341
|
+
address(this).toHexString(),
|
|
342
|
+
"/"
|
|
343
|
+
)
|
|
344
|
+
);
|
|
345
|
+
// initialize next project ID
|
|
346
|
+
_nextProjectId = _startingProjectId;
|
|
347
|
+
// emit PlatformUpdated(FIELD_NEXT_PROJECT_ID);
|
|
348
|
+
// register contract as an Engine contract
|
|
349
|
+
IEngineRegistryV0(_engineRegistryContract).registerContract(
|
|
350
|
+
address(this),
|
|
351
|
+
CORE_VERSION,
|
|
352
|
+
CORE_TYPE
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* @notice Mints a token from project `_projectId` and sets the
|
|
358
|
+
* token's owner to `_to`. Hash may or may not be assigned to the token
|
|
359
|
+
* during the mint transaction, depending on the randomizer contract.
|
|
360
|
+
* @param _to Address to be the minted token's owner.
|
|
361
|
+
* @param _projectId Project ID to mint a token on.
|
|
362
|
+
* @param _by Purchaser of minted token.
|
|
363
|
+
* @return _tokenId The ID of the minted token.
|
|
364
|
+
* @dev sender must be the allowed minterContract
|
|
365
|
+
* @dev name of function is optimized for gas usage
|
|
366
|
+
*/
|
|
367
|
+
function mint_Ecf(
|
|
368
|
+
address _to,
|
|
369
|
+
uint256 _projectId,
|
|
370
|
+
address _by
|
|
371
|
+
) external returns (uint256 _tokenId) {
|
|
372
|
+
// CHECKS
|
|
373
|
+
require(msg.sender == minterContract, "Must mint from minter contract");
|
|
374
|
+
Project storage project = projects[_projectId];
|
|
375
|
+
// load invocations into memory
|
|
376
|
+
uint24 invocationsBefore = project.invocations;
|
|
377
|
+
uint24 invocationsAfter;
|
|
378
|
+
unchecked {
|
|
379
|
+
// invocationsBefore guaranteed <= maxInvocations <= 1_000_000,
|
|
380
|
+
// 1_000_000 << max uint24, so no possible overflow
|
|
381
|
+
invocationsAfter = invocationsBefore + 1;
|
|
382
|
+
}
|
|
383
|
+
uint24 maxInvocations = project.maxInvocations;
|
|
384
|
+
|
|
385
|
+
require(
|
|
386
|
+
invocationsBefore < maxInvocations,
|
|
387
|
+
"Must not exceed max invocations"
|
|
388
|
+
);
|
|
389
|
+
require(
|
|
390
|
+
project.active ||
|
|
391
|
+
_by == projectIdToFinancials[_projectId].artistAddress,
|
|
392
|
+
"Project must exist and be active"
|
|
393
|
+
);
|
|
394
|
+
require(
|
|
395
|
+
!project.paused ||
|
|
396
|
+
_by == projectIdToFinancials[_projectId].artistAddress,
|
|
397
|
+
"Purchases are paused."
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
// EFFECTS
|
|
401
|
+
// increment project's invocations
|
|
402
|
+
project.invocations = invocationsAfter;
|
|
403
|
+
uint256 thisTokenId;
|
|
404
|
+
unchecked {
|
|
405
|
+
// invocationsBefore is uint24 << max uint256. In production use,
|
|
406
|
+
// _projectId * ONE_MILLION must be << max uint256, otherwise
|
|
407
|
+
// tokenIdToProjectId function become invalid.
|
|
408
|
+
// Therefore, no risk of overflow
|
|
409
|
+
thisTokenId = (_projectId * ONE_MILLION) + invocationsBefore;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// mark project as completed if hit max invocations
|
|
413
|
+
if (invocationsAfter == maxInvocations) {
|
|
414
|
+
_completeProject(_projectId);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// INTERACTIONS
|
|
418
|
+
_mint(_to, thisTokenId);
|
|
419
|
+
|
|
420
|
+
// token hash is updated by the randomizer contract on V3
|
|
421
|
+
randomizerContract.assignTokenHash(thisTokenId);
|
|
422
|
+
|
|
423
|
+
// Do not need to also log `projectId` in event, as the `projectId` for
|
|
424
|
+
// a given token can be derived from the `tokenId` with:
|
|
425
|
+
// projectId = tokenId / 1_000_000
|
|
426
|
+
// emit Mint(_to, thisTokenId);
|
|
427
|
+
|
|
428
|
+
return thisTokenId;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* @notice Sets the hash seed for a given token ID `_tokenId`.
|
|
433
|
+
* May only be called by the current randomizer contract.
|
|
434
|
+
* May only be called for tokens that have not already been assigned a
|
|
435
|
+
* non-zero hash.
|
|
436
|
+
* @param _tokenId Token ID to set the hash for.
|
|
437
|
+
* @param _hashSeed Hash seed to set for the token ID. Only last 12 bytes
|
|
438
|
+
* will be used.
|
|
439
|
+
* @dev gas-optimized function name because called during mint sequence
|
|
440
|
+
* @dev if a separate event is required when the token hash is set, e.g.
|
|
441
|
+
* for indexing purposes, it must be emitted by the randomizer. This is to
|
|
442
|
+
* minimize gas when minting.
|
|
443
|
+
*/
|
|
444
|
+
function setTokenHash_8PT(
|
|
445
|
+
uint256 _tokenId,
|
|
446
|
+
bytes32 _hashSeed
|
|
447
|
+
) external onlyValidTokenId(_tokenId) {
|
|
448
|
+
OwnerAndHashSeed storage ownerAndHashSeed = _ownersAndHashSeeds[
|
|
449
|
+
_tokenId
|
|
450
|
+
];
|
|
451
|
+
require(
|
|
452
|
+
msg.sender == address(randomizerContract),
|
|
453
|
+
"Only randomizer may set"
|
|
454
|
+
);
|
|
455
|
+
require(
|
|
456
|
+
ownerAndHashSeed.hashSeed == bytes12(0),
|
|
457
|
+
"Token hash already set"
|
|
458
|
+
);
|
|
459
|
+
require(_hashSeed != bytes12(0), "No zero hash seed");
|
|
460
|
+
ownerAndHashSeed.hashSeed = bytes12(_hashSeed);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* @notice Allows owner (AdminACL) to revoke ownership of the contract.
|
|
465
|
+
* Note that the contract is intended to continue to function after the
|
|
466
|
+
* owner renounces ownership, but no new projects will be able to be added.
|
|
467
|
+
* Renouncing ownership will leave the contract without an owner,
|
|
468
|
+
* thereby removing any functionality that is only available to the
|
|
469
|
+
* owner/AdminACL contract. The same is true for any dependent contracts
|
|
470
|
+
* that also integrate with the owner/AdminACL contract (e.g. potentially
|
|
471
|
+
* minter suite contracts, registry contracts, etc.).
|
|
472
|
+
* After renouncing ownership, artists will be in control of updates to
|
|
473
|
+
* their payment addresses and splits (see modifier
|
|
474
|
+
* onlyAdminACLOrRenouncedArtist`).
|
|
475
|
+
* While there is no currently intended reason to call this method based on
|
|
476
|
+
* typical Engine partner business practices, this method exists to allow
|
|
477
|
+
* artists to continue to maintain the limited set of contract
|
|
478
|
+
* functionality that exists post-project-lock in an environment in which
|
|
479
|
+
* there is no longer an admin maintaining this smart contract.
|
|
480
|
+
* @dev This function is intended to be called directly by the AdminACL,
|
|
481
|
+
* not by an address allowed by the AdminACL contract.
|
|
482
|
+
*/
|
|
483
|
+
function renounceOwnership() public override onlyOwner {
|
|
484
|
+
// broadcast that new projects are no longer allowed (if not already)
|
|
485
|
+
_forbidNewProjects();
|
|
486
|
+
// renounce ownership viw Ownable
|
|
487
|
+
Ownable.renounceOwnership();
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* @notice Updates reference to Art Blocks Dependency Registry contract.
|
|
492
|
+
* @param _artblocksDependencyRegistryAddress Address of new Dependency
|
|
493
|
+
* Registry.
|
|
494
|
+
*/
|
|
495
|
+
function updateArtblocksDependencyRegistryAddress(
|
|
496
|
+
address _artblocksDependencyRegistryAddress
|
|
497
|
+
)
|
|
498
|
+
external
|
|
499
|
+
onlyAdminACL(this.updateArtblocksDependencyRegistryAddress.selector)
|
|
500
|
+
onlyNonZeroAddress(_artblocksDependencyRegistryAddress)
|
|
501
|
+
{
|
|
502
|
+
artblocksDependencyRegistryAddress = _artblocksDependencyRegistryAddress;
|
|
503
|
+
// emit PlatformUpdated(FIELD_ARTBLOCKS_DEPENDENCY_REGISTRY_ADDRESS);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* @notice Updates sales addresses for the platform and render providers to
|
|
508
|
+
* the input parameters.
|
|
509
|
+
* @param _renderProviderPrimarySalesAddress Address of new primary sales
|
|
510
|
+
* payment address.
|
|
511
|
+
* @param _renderProviderSecondarySalesAddress Address of new secondary sales
|
|
512
|
+
* payment address.
|
|
513
|
+
* @param _platformProviderPrimarySalesAddress Address of new primary sales
|
|
514
|
+
* payment address.
|
|
515
|
+
* @param _platformProviderSecondarySalesAddress Address of new secondary sales
|
|
516
|
+
* payment address.
|
|
517
|
+
*/
|
|
518
|
+
function updateProviderSalesAddresses(
|
|
519
|
+
address payable _renderProviderPrimarySalesAddress,
|
|
520
|
+
address payable _renderProviderSecondarySalesAddress,
|
|
521
|
+
address payable _platformProviderPrimarySalesAddress,
|
|
522
|
+
address payable _platformProviderSecondarySalesAddress
|
|
523
|
+
)
|
|
524
|
+
external
|
|
525
|
+
onlyAdminACL(this.updateProviderSalesAddresses.selector)
|
|
526
|
+
onlyNonZeroAddress(_renderProviderPrimarySalesAddress)
|
|
527
|
+
onlyNonZeroAddress(_renderProviderSecondarySalesAddress)
|
|
528
|
+
onlyNonZeroAddress(_platformProviderPrimarySalesAddress)
|
|
529
|
+
onlyNonZeroAddress(_platformProviderSecondarySalesAddress)
|
|
530
|
+
{
|
|
531
|
+
_updateProviderSalesAddresses(
|
|
532
|
+
_renderProviderPrimarySalesAddress,
|
|
533
|
+
_renderProviderSecondarySalesAddress,
|
|
534
|
+
_platformProviderPrimarySalesAddress,
|
|
535
|
+
_platformProviderSecondarySalesAddress
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* @notice Updates the render and platform provider primary sales revenue percentage to
|
|
541
|
+
* the provided inputs.
|
|
542
|
+
* @param renderProviderPrimarySalesPercentage_ New primary sales revenue % for the render provider
|
|
543
|
+
* @param platformProviderPrimarySalesPercentage_ New primary sales revenue % for the platform provider
|
|
544
|
+
* percentage.
|
|
545
|
+
*/
|
|
546
|
+
function updateProviderPrimarySalesPercentages(
|
|
547
|
+
uint256 renderProviderPrimarySalesPercentage_,
|
|
548
|
+
uint256 platformProviderPrimarySalesPercentage_
|
|
549
|
+
)
|
|
550
|
+
external
|
|
551
|
+
onlyAdminACL(this.updateProviderPrimarySalesPercentages.selector)
|
|
552
|
+
{
|
|
553
|
+
// Validate that the sum of the proposed %s, does not exceed 100%.
|
|
554
|
+
require(
|
|
555
|
+
(renderProviderPrimarySalesPercentage_ +
|
|
556
|
+
platformProviderPrimarySalesPercentage_) <= ONE_HUNDRED,
|
|
557
|
+
"Max sum of ONE_HUNDRED %"
|
|
558
|
+
);
|
|
559
|
+
// Casting to `uint8` here is safe due check above, which does not allow
|
|
560
|
+
// overflow as of solidity version ^0.8.0.
|
|
561
|
+
_renderProviderPrimarySalesPercentage = uint8(
|
|
562
|
+
renderProviderPrimarySalesPercentage_
|
|
563
|
+
);
|
|
564
|
+
_platformProviderPrimarySalesPercentage = uint8(
|
|
565
|
+
platformProviderPrimarySalesPercentage_
|
|
566
|
+
);
|
|
567
|
+
// emit PlatformUpdated(FIELD_PROVIDER_PRIMARY_SALES_PERCENTAGES);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* @notice Updates render and platform provider secondary sales royalty Basis Points to
|
|
572
|
+
* the provided inputs.
|
|
573
|
+
* @param _renderProviderSecondarySalesBPS New secondary sales royalty Basis
|
|
574
|
+
* points.
|
|
575
|
+
* @param _platformProviderSecondarySalesBPS New secondary sales royalty Basis
|
|
576
|
+
* points.
|
|
577
|
+
* @dev Due to secondary royalties being ultimately enforced via social
|
|
578
|
+
* consensus, no hard upper limit is imposed on the BPS value, other than
|
|
579
|
+
* <= 100% royalty, which would not make mathematical sense. Realistically,
|
|
580
|
+
* changing this value is expected to either never occur, or be a rare
|
|
581
|
+
* occurrence.
|
|
582
|
+
*/
|
|
583
|
+
function updateProviderSecondarySalesBPS(
|
|
584
|
+
uint256 _renderProviderSecondarySalesBPS,
|
|
585
|
+
uint256 _platformProviderSecondarySalesBPS
|
|
586
|
+
) external onlyAdminACL(this.updateProviderSecondarySalesBPS.selector) {
|
|
587
|
+
// Validate that the sum of the proposed provider BPS, does not exceed 10_000 BPS.
|
|
588
|
+
require(
|
|
589
|
+
(_renderProviderSecondarySalesBPS +
|
|
590
|
+
_platformProviderSecondarySalesBPS) <=
|
|
591
|
+
MAX_PROVIDER_SECONDARY_SALES_BPS,
|
|
592
|
+
"Max sum of MAX_PROVIDER_SECONDARY_SALES_BPS BPS"
|
|
593
|
+
);
|
|
594
|
+
renderProviderSecondarySalesBPS = _renderProviderSecondarySalesBPS;
|
|
595
|
+
platformProviderSecondarySalesBPS = _platformProviderSecondarySalesBPS;
|
|
596
|
+
// emit PlatformUpdated(FIELD_PROVIDER_SECONDARY_SALES_BPS);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* @notice Updates minter to `_address`.
|
|
601
|
+
* @param _address Address of new minter.
|
|
602
|
+
*/
|
|
603
|
+
function updateMinterContract(
|
|
604
|
+
address _address
|
|
605
|
+
)
|
|
606
|
+
external
|
|
607
|
+
onlyAdminACL(this.updateMinterContract.selector)
|
|
608
|
+
onlyNonZeroAddress(_address)
|
|
609
|
+
{
|
|
610
|
+
minterContract = _address;
|
|
611
|
+
// emit MinterUpdated(_address);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* @notice Updates randomizer to `_randomizerAddress`.
|
|
616
|
+
* @param _randomizerAddress Address of new randomizer.
|
|
617
|
+
*/
|
|
618
|
+
function updateRandomizerAddress(
|
|
619
|
+
address _randomizerAddress
|
|
620
|
+
)
|
|
621
|
+
external
|
|
622
|
+
onlyAdminACL(this.updateRandomizerAddress.selector)
|
|
623
|
+
onlyNonZeroAddress(_randomizerAddress)
|
|
624
|
+
{
|
|
625
|
+
_updateRandomizerAddress(_randomizerAddress);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* @notice Toggles project `_projectId` as active/inactive.
|
|
630
|
+
* @param _projectId Project ID to be toggled.
|
|
631
|
+
*/
|
|
632
|
+
function toggleProjectIsActive(
|
|
633
|
+
uint256 _projectId
|
|
634
|
+
)
|
|
635
|
+
external
|
|
636
|
+
onlyAdminACL(this.toggleProjectIsActive.selector)
|
|
637
|
+
onlyValidProjectId(_projectId)
|
|
638
|
+
{
|
|
639
|
+
projects[_projectId].active = !projects[_projectId].active;
|
|
640
|
+
// emit ProjectUpdated(_projectId, FIELD_PROJECT_ACTIVE);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* @notice Artist proposes updated set of artist address, additional payee
|
|
645
|
+
* addresses, and percentage splits for project `_projectId`. Addresses and
|
|
646
|
+
* percentages do not have to all be changed, but they must all be defined
|
|
647
|
+
* as a complete set.
|
|
648
|
+
* Note that if the artist is only proposing a change to the payee percentage
|
|
649
|
+
* splits, without modifying the payee addresses, the proposal will be
|
|
650
|
+
* automatically approved and the new splits will become active immediately.
|
|
651
|
+
* Automatic approval will also be granted if the artist is only removing
|
|
652
|
+
* additional payee addresses, without adding any new ones.
|
|
653
|
+
* Also note that if `autoApproveArtistSplitProposals` is true, proposals
|
|
654
|
+
* will always be auto-approved, regardless of what is being changed.
|
|
655
|
+
* Also note that if the artist is proposing sending funds to the zero
|
|
656
|
+
* address, this function will revert and the proposal will not be created.
|
|
657
|
+
* @param _projectId Project ID.
|
|
658
|
+
* @param _artistAddress Artist address that controls the project, and may
|
|
659
|
+
* receive payments.
|
|
660
|
+
* @param _additionalPayeePrimarySales Address that may receive a
|
|
661
|
+
* percentage split of the artist's primary sales revenue.
|
|
662
|
+
* @param _additionalPayeePrimarySalesPercentage Percent of artist's
|
|
663
|
+
* portion of primary sale revenue that will be split to address
|
|
664
|
+
* `_additionalPayeePrimarySales`.
|
|
665
|
+
* @param _additionalPayeeSecondarySales Address that may receive a percentage
|
|
666
|
+
* split of the secondary sales royalties.
|
|
667
|
+
* @param _additionalPayeeSecondarySalesPercentage Percent of artist's portion
|
|
668
|
+
* of secondary sale royalties that will be split to address
|
|
669
|
+
* `_additionalPayeeSecondarySales`.
|
|
670
|
+
* @dev `_artistAddress` must be a valid address (non-zero-address), but it
|
|
671
|
+
* is intentionally allowable for `_additionalPayee{Primary,Secondaary}Sales`
|
|
672
|
+
* and their associated percentages to be zero'd out by the controlling artist.
|
|
673
|
+
*/
|
|
674
|
+
function proposeArtistPaymentAddressesAndSplits(
|
|
675
|
+
uint256 _projectId,
|
|
676
|
+
address payable _artistAddress,
|
|
677
|
+
address payable _additionalPayeePrimarySales,
|
|
678
|
+
uint256 _additionalPayeePrimarySalesPercentage,
|
|
679
|
+
address payable _additionalPayeeSecondarySales,
|
|
680
|
+
uint256 _additionalPayeeSecondarySalesPercentage
|
|
681
|
+
)
|
|
682
|
+
external
|
|
683
|
+
onlyValidProjectId(_projectId)
|
|
684
|
+
onlyArtist(_projectId)
|
|
685
|
+
onlyNonZeroAddress(_artistAddress)
|
|
686
|
+
{
|
|
687
|
+
ProjectFinance storage projectFinance = projectIdToFinancials[
|
|
688
|
+
_projectId
|
|
689
|
+
];
|
|
690
|
+
// checks
|
|
691
|
+
require(
|
|
692
|
+
_additionalPayeePrimarySalesPercentage <= ONE_HUNDRED &&
|
|
693
|
+
_additionalPayeeSecondarySalesPercentage <= ONE_HUNDRED,
|
|
694
|
+
"Max of 100%"
|
|
695
|
+
);
|
|
696
|
+
require(
|
|
697
|
+
_additionalPayeePrimarySalesPercentage == 0 ||
|
|
698
|
+
_additionalPayeePrimarySales != address(0),
|
|
699
|
+
"Primary payee is zero address"
|
|
700
|
+
);
|
|
701
|
+
require(
|
|
702
|
+
_additionalPayeeSecondarySalesPercentage == 0 ||
|
|
703
|
+
_additionalPayeeSecondarySales != address(0),
|
|
704
|
+
"Secondary payee is zero address"
|
|
705
|
+
);
|
|
706
|
+
// effects
|
|
707
|
+
// emit event for off-chain indexing
|
|
708
|
+
// note: always emit a proposal event, even in the pathway of
|
|
709
|
+
// automatic approval, to simplify indexing expectations
|
|
710
|
+
// emit ProposedArtistAddressesAndSplits(
|
|
711
|
+
// _projectId,
|
|
712
|
+
// _artistAddress,
|
|
713
|
+
// _additionalPayeePrimarySales,
|
|
714
|
+
// _additionalPayeePrimarySalesPercentage,
|
|
715
|
+
// _additionalPayeeSecondarySales,
|
|
716
|
+
// _additionalPayeeSecondarySalesPercentage
|
|
717
|
+
// );
|
|
718
|
+
// automatically accept if no proposed addresses modifications, or if
|
|
719
|
+
// the proposal only removes payee addresses, or if contract is set to
|
|
720
|
+
// always auto-approve.
|
|
721
|
+
// store proposal hash on-chain, only if not automatic accept
|
|
722
|
+
bool automaticAccept = autoApproveArtistSplitProposals;
|
|
723
|
+
if (!automaticAccept) {
|
|
724
|
+
// block scope to avoid stack too deep error
|
|
725
|
+
bool artistUnchanged = _artistAddress ==
|
|
726
|
+
projectFinance.artistAddress;
|
|
727
|
+
bool additionalPrimaryUnchangedOrRemoved = (_additionalPayeePrimarySales ==
|
|
728
|
+
projectFinance.additionalPayeePrimarySales) ||
|
|
729
|
+
(_additionalPayeePrimarySales == address(0));
|
|
730
|
+
bool additionalSecondaryUnchangedOrRemoved = (_additionalPayeeSecondarySales ==
|
|
731
|
+
projectFinance.additionalPayeeSecondarySales) ||
|
|
732
|
+
(_additionalPayeeSecondarySales == address(0));
|
|
733
|
+
automaticAccept =
|
|
734
|
+
artistUnchanged &&
|
|
735
|
+
additionalPrimaryUnchangedOrRemoved &&
|
|
736
|
+
additionalSecondaryUnchangedOrRemoved;
|
|
737
|
+
}
|
|
738
|
+
if (automaticAccept) {
|
|
739
|
+
// clear any previously proposed values
|
|
740
|
+
proposedArtistAddressesAndSplitsHash[_projectId] = bytes32(0);
|
|
741
|
+
// update storage
|
|
742
|
+
// artist address can change during automatic accept if
|
|
743
|
+
// autoApproveArtistSplitProposals is true
|
|
744
|
+
projectFinance.artistAddress = _artistAddress;
|
|
745
|
+
projectFinance
|
|
746
|
+
.additionalPayeePrimarySales = _additionalPayeePrimarySales;
|
|
747
|
+
// safe to cast as uint8 as max is 100%, max uint8 is 255
|
|
748
|
+
projectFinance.additionalPayeePrimarySalesPercentage = uint8(
|
|
749
|
+
_additionalPayeePrimarySalesPercentage
|
|
750
|
+
);
|
|
751
|
+
projectFinance
|
|
752
|
+
.additionalPayeeSecondarySales = _additionalPayeeSecondarySales;
|
|
753
|
+
// safe to cast as uint8 as max is 100%, max uint8 is 255
|
|
754
|
+
projectFinance.additionalPayeeSecondarySalesPercentage = uint8(
|
|
755
|
+
_additionalPayeeSecondarySalesPercentage
|
|
756
|
+
);
|
|
757
|
+
// emit event for off-chain indexing
|
|
758
|
+
// emit AcceptedArtistAddressesAndSplits(_projectId);
|
|
759
|
+
} else {
|
|
760
|
+
proposedArtistAddressesAndSplitsHash[_projectId] = keccak256(
|
|
761
|
+
abi.encode(
|
|
762
|
+
_artistAddress,
|
|
763
|
+
_additionalPayeePrimarySales,
|
|
764
|
+
_additionalPayeePrimarySalesPercentage,
|
|
765
|
+
_additionalPayeeSecondarySales,
|
|
766
|
+
_additionalPayeeSecondarySalesPercentage
|
|
767
|
+
)
|
|
768
|
+
);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* @notice Admin accepts a proposed set of updated artist address,
|
|
774
|
+
* additional payee addresses, and percentage splits for project
|
|
775
|
+
* `_projectId`. Addresses and percentages do not have to all be changed,
|
|
776
|
+
* but they must all be defined as a complete set.
|
|
777
|
+
* @param _projectId Project ID.
|
|
778
|
+
* @param _artistAddress Artist address that controls the project, and may
|
|
779
|
+
* receive payments.
|
|
780
|
+
* @param _additionalPayeePrimarySales Address that may receive a
|
|
781
|
+
* percentage split of the artist's primary sales revenue.
|
|
782
|
+
* @param _additionalPayeePrimarySalesPercentage Percent of artist's
|
|
783
|
+
* portion of primary sale revenue that will be split to address
|
|
784
|
+
* `_additionalPayeePrimarySales`.
|
|
785
|
+
* @param _additionalPayeeSecondarySales Address that may receive a percentage
|
|
786
|
+
* split of the secondary sales royalties.
|
|
787
|
+
* @param _additionalPayeeSecondarySalesPercentage Percent of artist's portion
|
|
788
|
+
* of secondary sale royalties that will be split to address
|
|
789
|
+
* `_additionalPayeeSecondarySales`.
|
|
790
|
+
* @dev this must be called by the Admin ACL contract, and must only accept
|
|
791
|
+
* the most recent proposed values for a given project (validated on-chain
|
|
792
|
+
* by comparing the hash of the proposed and accepted values).
|
|
793
|
+
* @dev `_artistAddress` must be a valid address (non-zero-address), but it
|
|
794
|
+
* is intentionally allowable for `_additionalPayee{Primary,Secondaary}Sales`
|
|
795
|
+
* and their associated percentages to be zero'd out by the controlling artist.
|
|
796
|
+
*/
|
|
797
|
+
function adminAcceptArtistAddressesAndSplits(
|
|
798
|
+
uint256 _projectId,
|
|
799
|
+
address payable _artistAddress,
|
|
800
|
+
address payable _additionalPayeePrimarySales,
|
|
801
|
+
uint256 _additionalPayeePrimarySalesPercentage,
|
|
802
|
+
address payable _additionalPayeeSecondarySales,
|
|
803
|
+
uint256 _additionalPayeeSecondarySalesPercentage
|
|
804
|
+
)
|
|
805
|
+
external
|
|
806
|
+
onlyValidProjectId(_projectId)
|
|
807
|
+
onlyAdminACLOrRenouncedArtist(
|
|
808
|
+
_projectId,
|
|
809
|
+
this.adminAcceptArtistAddressesAndSplits.selector
|
|
810
|
+
)
|
|
811
|
+
onlyNonZeroAddress(_artistAddress)
|
|
812
|
+
{
|
|
813
|
+
// checks
|
|
814
|
+
require(
|
|
815
|
+
proposedArtistAddressesAndSplitsHash[_projectId] ==
|
|
816
|
+
keccak256(
|
|
817
|
+
abi.encode(
|
|
818
|
+
_artistAddress,
|
|
819
|
+
_additionalPayeePrimarySales,
|
|
820
|
+
_additionalPayeePrimarySalesPercentage,
|
|
821
|
+
_additionalPayeeSecondarySales,
|
|
822
|
+
_additionalPayeeSecondarySalesPercentage
|
|
823
|
+
)
|
|
824
|
+
),
|
|
825
|
+
"Must match artist proposal"
|
|
826
|
+
);
|
|
827
|
+
// effects
|
|
828
|
+
ProjectFinance storage projectFinance = projectIdToFinancials[
|
|
829
|
+
_projectId
|
|
830
|
+
];
|
|
831
|
+
projectFinance.artistAddress = _artistAddress;
|
|
832
|
+
projectFinance
|
|
833
|
+
.additionalPayeePrimarySales = _additionalPayeePrimarySales;
|
|
834
|
+
projectFinance.additionalPayeePrimarySalesPercentage = uint8(
|
|
835
|
+
_additionalPayeePrimarySalesPercentage
|
|
836
|
+
);
|
|
837
|
+
projectFinance
|
|
838
|
+
.additionalPayeeSecondarySales = _additionalPayeeSecondarySales;
|
|
839
|
+
projectFinance.additionalPayeeSecondarySalesPercentage = uint8(
|
|
840
|
+
_additionalPayeeSecondarySalesPercentage
|
|
841
|
+
);
|
|
842
|
+
// clear proposed values
|
|
843
|
+
proposedArtistAddressesAndSplitsHash[_projectId] = bytes32(0);
|
|
844
|
+
// emit event for off-chain indexing
|
|
845
|
+
// emit AcceptedArtistAddressesAndSplits(_projectId);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* @notice Updates artist of project `_projectId` to `_artistAddress`.
|
|
850
|
+
* This is to only be used in the event that the artist address is
|
|
851
|
+
* compromised or sanctioned.
|
|
852
|
+
* @param _projectId Project ID.
|
|
853
|
+
* @param _artistAddress New artist address.
|
|
854
|
+
*/
|
|
855
|
+
function updateProjectArtistAddress(
|
|
856
|
+
uint256 _projectId,
|
|
857
|
+
address payable _artistAddress
|
|
858
|
+
)
|
|
859
|
+
external
|
|
860
|
+
onlyValidProjectId(_projectId)
|
|
861
|
+
onlyAdminACLOrRenouncedArtist(
|
|
862
|
+
_projectId,
|
|
863
|
+
this.updateProjectArtistAddress.selector
|
|
864
|
+
)
|
|
865
|
+
onlyNonZeroAddress(_artistAddress)
|
|
866
|
+
{
|
|
867
|
+
projectIdToFinancials[_projectId].artistAddress = _artistAddress;
|
|
868
|
+
// emit ProjectUpdated(_projectId, FIELD_PROJECT_ARTIST_ADDRESS);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* @notice Toggles paused state of project `_projectId`.
|
|
873
|
+
* @param _projectId Project ID to be toggled.
|
|
874
|
+
*/
|
|
875
|
+
function toggleProjectIsPaused(
|
|
876
|
+
uint256 _projectId
|
|
877
|
+
) external onlyArtist(_projectId) {
|
|
878
|
+
projects[_projectId].paused = !projects[_projectId].paused;
|
|
879
|
+
// emit ProjectUpdated(_projectId, FIELD_PROJECT_PAUSED);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
/**
|
|
883
|
+
* @notice Adds new project `_projectName` by `_artistAddress`.
|
|
884
|
+
* @param _projectName Project name.
|
|
885
|
+
* @param _artistAddress Artist's address.
|
|
886
|
+
* @dev token price now stored on minter
|
|
887
|
+
*/
|
|
888
|
+
function addProject(
|
|
889
|
+
string memory _projectName,
|
|
890
|
+
address payable _artistAddress
|
|
891
|
+
)
|
|
892
|
+
external
|
|
893
|
+
onlyAdminACL(this.addProject.selector)
|
|
894
|
+
onlyNonEmptyString(_projectName)
|
|
895
|
+
onlyNonZeroAddress(_artistAddress)
|
|
896
|
+
{
|
|
897
|
+
require(!newProjectsForbidden, "New projects forbidden");
|
|
898
|
+
uint256 projectId = _nextProjectId;
|
|
899
|
+
projectIdToFinancials[projectId].artistAddress = _artistAddress;
|
|
900
|
+
projects[projectId].name = _projectName;
|
|
901
|
+
projects[projectId].paused = true;
|
|
902
|
+
projects[projectId].maxInvocations = ONE_MILLION_UINT24;
|
|
903
|
+
projects[projectId].projectBaseURI = defaultBaseURI;
|
|
904
|
+
|
|
905
|
+
_nextProjectId = uint248(projectId) + 1;
|
|
906
|
+
// emit ProjectUpdated(projectId, FIELD_PROJECT_CREATED);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
/**
|
|
910
|
+
* @notice Forever forbids new projects from being added to this contract.
|
|
911
|
+
*/
|
|
912
|
+
function forbidNewProjects()
|
|
913
|
+
external
|
|
914
|
+
onlyAdminACL(this.forbidNewProjects.selector)
|
|
915
|
+
{
|
|
916
|
+
require(!newProjectsForbidden, "Already forbidden");
|
|
917
|
+
_forbidNewProjects();
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
/**
|
|
921
|
+
* @notice Updates name of project `_projectId` to be `_projectName`.
|
|
922
|
+
* @param _projectId Project ID.
|
|
923
|
+
* @param _projectName New project name.
|
|
924
|
+
*/
|
|
925
|
+
function updateProjectName(
|
|
926
|
+
uint256 _projectId,
|
|
927
|
+
string memory _projectName
|
|
928
|
+
)
|
|
929
|
+
external
|
|
930
|
+
onlyUnlocked(_projectId)
|
|
931
|
+
onlyArtistOrAdminACL(_projectId, this.updateProjectName.selector)
|
|
932
|
+
onlyNonEmptyString(_projectName)
|
|
933
|
+
{
|
|
934
|
+
projects[_projectId].name = _projectName;
|
|
935
|
+
// emit ProjectUpdated(_projectId, FIELD_PROJECT_NAME);
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/**
|
|
939
|
+
* @notice Updates artist name for project `_projectId` to be
|
|
940
|
+
* `_projectArtistName`.
|
|
941
|
+
* @param _projectId Project ID.
|
|
942
|
+
* @param _projectArtistName New artist name.
|
|
943
|
+
*/
|
|
944
|
+
function updateProjectArtistName(
|
|
945
|
+
uint256 _projectId,
|
|
946
|
+
string memory _projectArtistName
|
|
947
|
+
)
|
|
948
|
+
external
|
|
949
|
+
onlyUnlocked(_projectId)
|
|
950
|
+
onlyArtistOrAdminACL(_projectId, this.updateProjectArtistName.selector)
|
|
951
|
+
onlyNonEmptyString(_projectArtistName)
|
|
952
|
+
{
|
|
953
|
+
projects[_projectId].artist = _projectArtistName;
|
|
954
|
+
// emit ProjectUpdated(_projectId, FIELD_PROJECT_ARTIST_NAME);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/**
|
|
958
|
+
* @notice Updates artist secondary market royalties for project
|
|
959
|
+
* `_projectId` to be `_secondMarketRoyalty` percent.
|
|
960
|
+
* This DOES NOT include the secondary market royalty percentages collected
|
|
961
|
+
* by the issuing platform; it is only the total percentage of royalties
|
|
962
|
+
* that will be split to artist and additionalSecondaryPayee.
|
|
963
|
+
* @param _projectId Project ID.
|
|
964
|
+
* @param _secondMarketRoyalty Percent of secondary sales revenue that will
|
|
965
|
+
* be split to artist and additionalSecondaryPayee. This must be less than
|
|
966
|
+
* or equal to ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE percent.
|
|
967
|
+
*/
|
|
968
|
+
function updateProjectSecondaryMarketRoyaltyPercentage(
|
|
969
|
+
uint256 _projectId,
|
|
970
|
+
uint256 _secondMarketRoyalty
|
|
971
|
+
) external onlyArtist(_projectId) {
|
|
972
|
+
require(
|
|
973
|
+
_secondMarketRoyalty <= ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE,
|
|
974
|
+
"Max of ARTIST_MAX_SECONDARY_ROYALTY_PERCENTAGE percent"
|
|
975
|
+
);
|
|
976
|
+
projectIdToFinancials[_projectId]
|
|
977
|
+
.secondaryMarketRoyaltyPercentage = uint8(_secondMarketRoyalty);
|
|
978
|
+
// emit ProjectUpdated(
|
|
979
|
+
// _projectId,
|
|
980
|
+
// FIELD_PROJECT_SECONDARY_MARKET_ROYALTY_PERCENTAGE
|
|
981
|
+
// );
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* @notice Updates description of project `_projectId`.
|
|
986
|
+
* Only artist may call when unlocked, only admin may call when locked.
|
|
987
|
+
* @param _projectId Project ID.
|
|
988
|
+
* @param _projectDescription New project description.
|
|
989
|
+
*/
|
|
990
|
+
function updateProjectDescription(
|
|
991
|
+
uint256 _projectId,
|
|
992
|
+
string memory _projectDescription
|
|
993
|
+
) external {
|
|
994
|
+
// checks
|
|
995
|
+
require(
|
|
996
|
+
_projectUnlocked(_projectId)
|
|
997
|
+
? msg.sender == projectIdToFinancials[_projectId].artistAddress
|
|
998
|
+
: adminACLAllowed(
|
|
999
|
+
msg.sender,
|
|
1000
|
+
address(this),
|
|
1001
|
+
this.updateProjectDescription.selector
|
|
1002
|
+
),
|
|
1003
|
+
"Only artist when unlocked, owner when locked"
|
|
1004
|
+
);
|
|
1005
|
+
// effects
|
|
1006
|
+
projects[_projectId].description = _projectDescription;
|
|
1007
|
+
// emit ProjectUpdated(_projectId, FIELD_PROJECT_DESCRIPTION);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
/**
|
|
1011
|
+
* @notice Updates website of project `_projectId` to be `_projectWebsite`.
|
|
1012
|
+
* @param _projectId Project ID.
|
|
1013
|
+
* @param _projectWebsite New project website.
|
|
1014
|
+
* @dev It is intentionally allowed for this to be set to the empty string.
|
|
1015
|
+
*/
|
|
1016
|
+
function updateProjectWebsite(
|
|
1017
|
+
uint256 _projectId,
|
|
1018
|
+
string memory _projectWebsite
|
|
1019
|
+
) external onlyArtist(_projectId) {
|
|
1020
|
+
projects[_projectId].website = _projectWebsite;
|
|
1021
|
+
// emit ProjectUpdated(_projectId, FIELD_PROJECT_WEBSITE);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
/**
|
|
1025
|
+
* @notice Updates license for project `_projectId`.
|
|
1026
|
+
* @param _projectId Project ID.
|
|
1027
|
+
* @param _projectLicense New project license.
|
|
1028
|
+
*/
|
|
1029
|
+
function updateProjectLicense(
|
|
1030
|
+
uint256 _projectId,
|
|
1031
|
+
string memory _projectLicense
|
|
1032
|
+
)
|
|
1033
|
+
external
|
|
1034
|
+
onlyUnlocked(_projectId)
|
|
1035
|
+
onlyArtistOrAdminACL(_projectId, this.updateProjectLicense.selector)
|
|
1036
|
+
onlyNonEmptyString(_projectLicense)
|
|
1037
|
+
{
|
|
1038
|
+
projects[_projectId].license = _projectLicense;
|
|
1039
|
+
// emit ProjectUpdated(_projectId, FIELD_PROJECT_LICENSE);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
/**
|
|
1043
|
+
* @notice Updates maximum invocations for project `_projectId` to
|
|
1044
|
+
* `_maxInvocations`. Maximum invocations may only be decreased by the
|
|
1045
|
+
* artist, and must be greater than or equal to current invocations.
|
|
1046
|
+
* New projects are created with maximum invocations of 1 million by
|
|
1047
|
+
* default.
|
|
1048
|
+
* @param _projectId Project ID.
|
|
1049
|
+
* @param _maxInvocations New maximum invocations.
|
|
1050
|
+
*/
|
|
1051
|
+
function updateProjectMaxInvocations(
|
|
1052
|
+
uint256 _projectId,
|
|
1053
|
+
uint24 _maxInvocations
|
|
1054
|
+
) external onlyArtist(_projectId) {
|
|
1055
|
+
// CHECKS
|
|
1056
|
+
Project storage project = projects[_projectId];
|
|
1057
|
+
uint256 _invocations = project.invocations;
|
|
1058
|
+
require(
|
|
1059
|
+
(_maxInvocations < project.maxInvocations),
|
|
1060
|
+
"maxInvocations may only be decreased"
|
|
1061
|
+
);
|
|
1062
|
+
require(
|
|
1063
|
+
_maxInvocations >= _invocations,
|
|
1064
|
+
"Only max invocations gte current invocations"
|
|
1065
|
+
);
|
|
1066
|
+
// EFFECTS
|
|
1067
|
+
project.maxInvocations = _maxInvocations;
|
|
1068
|
+
// emit ProjectUpdated(_projectId, FIELD_PROJECT_MAX_INVOCATIONS);
|
|
1069
|
+
|
|
1070
|
+
// register completed timestamp if action completed the project
|
|
1071
|
+
if (_maxInvocations == _invocations) {
|
|
1072
|
+
_completeProject(_projectId);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
/**
|
|
1077
|
+
* @notice Adds a script to project `_projectId`.
|
|
1078
|
+
* @param _projectId Project to be updated.
|
|
1079
|
+
* @param _script Script to be added. Required to be a non-empty string,
|
|
1080
|
+
* but no further validation is performed.
|
|
1081
|
+
*/
|
|
1082
|
+
function addProjectScript(
|
|
1083
|
+
uint256 _projectId,
|
|
1084
|
+
string memory _script
|
|
1085
|
+
)
|
|
1086
|
+
external
|
|
1087
|
+
onlyUnlocked(_projectId)
|
|
1088
|
+
onlyArtistOrAdminACL(_projectId, this.addProjectScript.selector)
|
|
1089
|
+
onlyNonEmptyString(_script)
|
|
1090
|
+
{
|
|
1091
|
+
Project storage project = projects[_projectId];
|
|
1092
|
+
// store script in contract bytecode
|
|
1093
|
+
project.scriptBytecodeAddresses[project.scriptCount] = _script
|
|
1094
|
+
.writeToBytecode();
|
|
1095
|
+
project.scriptCount = project.scriptCount + 1;
|
|
1096
|
+
// emit ProjectUpdated(_projectId, FIELD_PROJECT_SCRIPT);
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
/**
|
|
1100
|
+
* @notice Updates script for project `_projectId` at script ID `_scriptId`.
|
|
1101
|
+
* @param _projectId Project to be updated.
|
|
1102
|
+
* @param _scriptId Script ID to be updated.
|
|
1103
|
+
* @param _script The updated script value. Required to be a non-empty
|
|
1104
|
+
* string, but no further validation is performed.
|
|
1105
|
+
*/
|
|
1106
|
+
function updateProjectScript(
|
|
1107
|
+
uint256 _projectId,
|
|
1108
|
+
uint256 _scriptId,
|
|
1109
|
+
string memory _script
|
|
1110
|
+
)
|
|
1111
|
+
external
|
|
1112
|
+
onlyUnlocked(_projectId)
|
|
1113
|
+
onlyArtistOrAdminACL(_projectId, this.updateProjectScript.selector)
|
|
1114
|
+
onlyNonEmptyString(_script)
|
|
1115
|
+
{
|
|
1116
|
+
Project storage project = projects[_projectId];
|
|
1117
|
+
require(_scriptId < project.scriptCount, "scriptId out of range");
|
|
1118
|
+
// purge old contract bytecode contract from the blockchain state
|
|
1119
|
+
// note: Although this does reduce usage of Ethereum state, it does not
|
|
1120
|
+
// reduce the gas costs of removal transactions. We believe this is the
|
|
1121
|
+
// best behavior at the time of writing, and do not expect this to
|
|
1122
|
+
// result in any breaking changes in the future. All current proposals
|
|
1123
|
+
// to change the self-destruct opcode are backwards compatible, but may
|
|
1124
|
+
// result in not removing the bytecode from the blockchain state. This
|
|
1125
|
+
// implementation is compatible with that architecture, as it does not
|
|
1126
|
+
// rely on the bytecode being removed from the blockchain state.
|
|
1127
|
+
project.scriptBytecodeAddresses[_scriptId].purgeBytecode();
|
|
1128
|
+
// store script in contract bytecode, replacing reference address from
|
|
1129
|
+
// the contract that no longer exists with the newly created one
|
|
1130
|
+
project.scriptBytecodeAddresses[_scriptId] = _script.writeToBytecode();
|
|
1131
|
+
// emit ProjectUpdated(_projectId, FIELD_PROJECT_SCRIPT);
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
/**
|
|
1135
|
+
* @notice Removes last script from project `_projectId`.
|
|
1136
|
+
* @param _projectId Project to be updated.
|
|
1137
|
+
*/
|
|
1138
|
+
function removeProjectLastScript(
|
|
1139
|
+
uint256 _projectId
|
|
1140
|
+
)
|
|
1141
|
+
external
|
|
1142
|
+
onlyUnlocked(_projectId)
|
|
1143
|
+
onlyArtistOrAdminACL(_projectId, this.removeProjectLastScript.selector)
|
|
1144
|
+
{
|
|
1145
|
+
Project storage project = projects[_projectId];
|
|
1146
|
+
require(project.scriptCount > 0, "there are no scripts to remove");
|
|
1147
|
+
// purge old contract bytecode contract from the blockchain state
|
|
1148
|
+
// note: Although this does reduce usage of Ethereum state, it does not
|
|
1149
|
+
// reduce the gas costs of removal transactions. We believe this is the
|
|
1150
|
+
// best behavior at the time of writing, and do not expect this to
|
|
1151
|
+
// result in any breaking changes in the future. All current proposals
|
|
1152
|
+
// to change the self-destruct opcode are backwards compatible, but may
|
|
1153
|
+
// result in not removing the bytecode from the blockchain state. This
|
|
1154
|
+
// implementation is compatible with that architecture, as it does not
|
|
1155
|
+
// rely on the bytecode being removed from the blockchain state.
|
|
1156
|
+
project
|
|
1157
|
+
.scriptBytecodeAddresses[project.scriptCount - 1]
|
|
1158
|
+
.purgeBytecode();
|
|
1159
|
+
// delete reference to contract address that no longer exists
|
|
1160
|
+
delete project.scriptBytecodeAddresses[project.scriptCount - 1];
|
|
1161
|
+
unchecked {
|
|
1162
|
+
project.scriptCount = project.scriptCount - 1;
|
|
1163
|
+
}
|
|
1164
|
+
// emit ProjectUpdated(_projectId, FIELD_PROJECT_SCRIPT);
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
/**
|
|
1168
|
+
* @notice Updates script type for project `_projectId`.
|
|
1169
|
+
* @param _projectId Project to be updated.
|
|
1170
|
+
* @param _scriptTypeAndVersion Script type and version e.g. "p5js@1.0.0",
|
|
1171
|
+
* as bytes32 encoded string.
|
|
1172
|
+
*/
|
|
1173
|
+
function updateProjectScriptType(
|
|
1174
|
+
uint256 _projectId,
|
|
1175
|
+
bytes32 _scriptTypeAndVersion
|
|
1176
|
+
)
|
|
1177
|
+
external
|
|
1178
|
+
onlyUnlocked(_projectId)
|
|
1179
|
+
onlyArtistOrAdminACL(_projectId, this.updateProjectScriptType.selector)
|
|
1180
|
+
{
|
|
1181
|
+
Project storage project = projects[_projectId];
|
|
1182
|
+
// require exactly one @ symbol in _scriptTypeAndVersion
|
|
1183
|
+
require(
|
|
1184
|
+
_scriptTypeAndVersion.containsExactCharacterQty(
|
|
1185
|
+
AT_CHARACTER_CODE,
|
|
1186
|
+
uint8(1)
|
|
1187
|
+
),
|
|
1188
|
+
"must contain exactly one @"
|
|
1189
|
+
);
|
|
1190
|
+
project.scriptTypeAndVersion = _scriptTypeAndVersion;
|
|
1191
|
+
// emit ProjectUpdated(_projectId, FIELD_PROJECT_SCRIPT_TYPE);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
/**
|
|
1195
|
+
* @notice Updates project's aspect ratio.
|
|
1196
|
+
* @param _projectId Project to be updated.
|
|
1197
|
+
* @param _aspectRatio Aspect ratio to be set. Intended to be string in the
|
|
1198
|
+
* format of a decimal, e.g. "1" for square, "1.77777778" for 16:9, etc.,
|
|
1199
|
+
* allowing for a maximum of 10 digits and one (optional) decimal separator.
|
|
1200
|
+
*/
|
|
1201
|
+
function updateProjectAspectRatio(
|
|
1202
|
+
uint256 _projectId,
|
|
1203
|
+
string memory _aspectRatio
|
|
1204
|
+
)
|
|
1205
|
+
external
|
|
1206
|
+
onlyUnlocked(_projectId)
|
|
1207
|
+
onlyArtistOrAdminACL(_projectId, this.updateProjectAspectRatio.selector)
|
|
1208
|
+
onlyNonEmptyString(_aspectRatio)
|
|
1209
|
+
{
|
|
1210
|
+
// Perform more detailed input validation for aspect ratio.
|
|
1211
|
+
bytes memory aspectRatioBytes = bytes(_aspectRatio);
|
|
1212
|
+
uint256 bytesLength = aspectRatioBytes.length;
|
|
1213
|
+
require(bytesLength <= 11, "Aspect ratio format too long");
|
|
1214
|
+
bool hasSeenDecimalSeparator = false;
|
|
1215
|
+
bool hasSeenNumber = false;
|
|
1216
|
+
for (uint256 i; i < bytesLength; i++) {
|
|
1217
|
+
bytes1 character = aspectRatioBytes[i];
|
|
1218
|
+
// Allow as many #s as desired.
|
|
1219
|
+
if (character >= 0x30 && character <= 0x39) {
|
|
1220
|
+
// 9-0
|
|
1221
|
+
// We need to ensure there is at least 1 `9-0` occurrence.
|
|
1222
|
+
hasSeenNumber = true;
|
|
1223
|
+
continue;
|
|
1224
|
+
}
|
|
1225
|
+
if (character == 0x2E) {
|
|
1226
|
+
// .
|
|
1227
|
+
// Allow no more than 1 `.` occurrence.
|
|
1228
|
+
if (!hasSeenDecimalSeparator) {
|
|
1229
|
+
hasSeenDecimalSeparator = true;
|
|
1230
|
+
continue;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
revert("Improperly formatted aspect ratio");
|
|
1234
|
+
}
|
|
1235
|
+
require(hasSeenNumber, "Aspect ratio has no numbers");
|
|
1236
|
+
|
|
1237
|
+
projects[_projectId].aspectRatio = _aspectRatio;
|
|
1238
|
+
// emit ProjectUpdated(_projectId, FIELD_PROJECT_ASPECT_RATIO);
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
/**
|
|
1242
|
+
* @notice Updates base URI for project `_projectId` to `_newBaseURI`.
|
|
1243
|
+
* This is the controlling base URI for all tokens in the project. The
|
|
1244
|
+
* contract-level defaultBaseURI is only used when initializing new
|
|
1245
|
+
* projects.
|
|
1246
|
+
* @param _projectId Project to be updated.
|
|
1247
|
+
* @param _newBaseURI New base URI.
|
|
1248
|
+
*/
|
|
1249
|
+
function updateProjectBaseURI(
|
|
1250
|
+
uint256 _projectId,
|
|
1251
|
+
string memory _newBaseURI
|
|
1252
|
+
) external onlyArtist(_projectId) onlyNonEmptyString(_newBaseURI) {
|
|
1253
|
+
projects[_projectId].projectBaseURI = _newBaseURI;
|
|
1254
|
+
// emit ProjectUpdated(_projectId, FIELD_PROJECT_BASE_URI);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
/**
|
|
1258
|
+
* @notice Updates default base URI to `_defaultBaseURI`. The
|
|
1259
|
+
* contract-level defaultBaseURI is only used when initializing new
|
|
1260
|
+
* projects. Token URIs are determined by their project's `projectBaseURI`.
|
|
1261
|
+
* @param _defaultBaseURI New default base URI.
|
|
1262
|
+
*/
|
|
1263
|
+
function updateDefaultBaseURI(
|
|
1264
|
+
string memory _defaultBaseURI
|
|
1265
|
+
)
|
|
1266
|
+
external
|
|
1267
|
+
onlyAdminACL(this.updateDefaultBaseURI.selector)
|
|
1268
|
+
onlyNonEmptyString(_defaultBaseURI)
|
|
1269
|
+
{
|
|
1270
|
+
_updateDefaultBaseURI(_defaultBaseURI);
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
/**
|
|
1274
|
+
* @notice Next project ID to be created on this contract.
|
|
1275
|
+
* @return uint256 Next project ID.
|
|
1276
|
+
*/
|
|
1277
|
+
function nextProjectId() external view returns (uint256) {
|
|
1278
|
+
return _nextProjectId;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
/**
|
|
1282
|
+
* @notice Returns token hash for token ID `_tokenId`. Returns null if hash
|
|
1283
|
+
* has not been set.
|
|
1284
|
+
* @param _tokenId Token ID to be queried.
|
|
1285
|
+
* @return bytes32 Token hash.
|
|
1286
|
+
* @dev token hash is the keccak256 hash of the stored hash seed
|
|
1287
|
+
*/
|
|
1288
|
+
function tokenIdToHash(uint256 _tokenId) external view returns (bytes32) {
|
|
1289
|
+
bytes12 _hashSeed = _ownersAndHashSeeds[_tokenId].hashSeed;
|
|
1290
|
+
if (_hashSeed == 0) {
|
|
1291
|
+
return 0;
|
|
1292
|
+
}
|
|
1293
|
+
return keccak256(abi.encode(_hashSeed));
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
/**
|
|
1297
|
+
* @notice Returns token hash **seed** for token ID `_tokenId`. Returns
|
|
1298
|
+
* null if hash seed has not been set. The hash seed id the bytes12 value
|
|
1299
|
+
* which is hashed to produce the token hash.
|
|
1300
|
+
* @param _tokenId Token ID to be queried.
|
|
1301
|
+
* @return bytes12 Token hash seed.
|
|
1302
|
+
* @dev token hash seed is keccak256 hashed to give the token hash
|
|
1303
|
+
*/
|
|
1304
|
+
function tokenIdToHashSeed(
|
|
1305
|
+
uint256 _tokenId
|
|
1306
|
+
) external view returns (bytes12) {
|
|
1307
|
+
return _ownersAndHashSeeds[_tokenId].hashSeed;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
/**
|
|
1311
|
+
* @notice View function returning the render provider portion of
|
|
1312
|
+
* primary sales, in percent.
|
|
1313
|
+
* @return uint256 The render provider portion of primary sales,
|
|
1314
|
+
* in percent.
|
|
1315
|
+
*/
|
|
1316
|
+
function renderProviderPrimarySalesPercentage()
|
|
1317
|
+
external
|
|
1318
|
+
view
|
|
1319
|
+
returns (uint256)
|
|
1320
|
+
{
|
|
1321
|
+
return _renderProviderPrimarySalesPercentage;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
/**
|
|
1325
|
+
* @notice View function returning the platform provider portion of
|
|
1326
|
+
* primary sales, in percent.
|
|
1327
|
+
* @return uint256 The platform provider portion of primary sales,
|
|
1328
|
+
* in percent.
|
|
1329
|
+
*/
|
|
1330
|
+
function platformProviderPrimarySalesPercentage()
|
|
1331
|
+
external
|
|
1332
|
+
view
|
|
1333
|
+
returns (uint256)
|
|
1334
|
+
{
|
|
1335
|
+
return _platformProviderPrimarySalesPercentage;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
/**
|
|
1339
|
+
* @notice View function returning Artist's address for project
|
|
1340
|
+
* `_projectId`.
|
|
1341
|
+
* @param _projectId Project ID to be queried.
|
|
1342
|
+
* @return address Artist's address.
|
|
1343
|
+
*/
|
|
1344
|
+
function projectIdToArtistAddress(
|
|
1345
|
+
uint256 _projectId
|
|
1346
|
+
) external view returns (address payable) {
|
|
1347
|
+
return projectIdToFinancials[_projectId].artistAddress;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
/**
|
|
1351
|
+
* @notice View function returning Artist's secondary market royalty
|
|
1352
|
+
* percentage for project `_projectId`.
|
|
1353
|
+
* This does not include render/platform providers portions of secondary
|
|
1354
|
+
* market royalties.
|
|
1355
|
+
* @param _projectId Project ID to be queried.
|
|
1356
|
+
* @return uint256 Artist's secondary market royalty percentage.
|
|
1357
|
+
*/
|
|
1358
|
+
function projectIdToSecondaryMarketRoyaltyPercentage(
|
|
1359
|
+
uint256 _projectId
|
|
1360
|
+
) external view returns (uint256) {
|
|
1361
|
+
return
|
|
1362
|
+
projectIdToFinancials[_projectId].secondaryMarketRoyaltyPercentage;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
/**
|
|
1366
|
+
* @notice View function returning Artist's additional payee address for
|
|
1367
|
+
* primary sales, for project `_projectId`.
|
|
1368
|
+
* @param _projectId Project ID to be queried.
|
|
1369
|
+
* @return address Artist's additional payee address for primary sales.
|
|
1370
|
+
*/
|
|
1371
|
+
function projectIdToAdditionalPayeePrimarySales(
|
|
1372
|
+
uint256 _projectId
|
|
1373
|
+
) external view returns (address payable) {
|
|
1374
|
+
return projectIdToFinancials[_projectId].additionalPayeePrimarySales;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
/**
|
|
1378
|
+
* @notice View function returning Artist's additional payee primary sales
|
|
1379
|
+
* percentage, for project `_projectId`.
|
|
1380
|
+
* @param _projectId Project ID to be queried.
|
|
1381
|
+
* @return uint256 Artist's additional payee primary sales percentage.
|
|
1382
|
+
*/
|
|
1383
|
+
function projectIdToAdditionalPayeePrimarySalesPercentage(
|
|
1384
|
+
uint256 _projectId
|
|
1385
|
+
) external view returns (uint256) {
|
|
1386
|
+
return
|
|
1387
|
+
projectIdToFinancials[_projectId]
|
|
1388
|
+
.additionalPayeePrimarySalesPercentage;
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
/**
|
|
1392
|
+
* @notice View function returning Artist's additional payee address for
|
|
1393
|
+
* secondary sales, for project `_projectId`.
|
|
1394
|
+
* @param _projectId Project ID to be queried.
|
|
1395
|
+
* @return address payable Artist's additional payee address for secondary
|
|
1396
|
+
* sales.
|
|
1397
|
+
*/
|
|
1398
|
+
function projectIdToAdditionalPayeeSecondarySales(
|
|
1399
|
+
uint256 _projectId
|
|
1400
|
+
) external view returns (address payable) {
|
|
1401
|
+
return projectIdToFinancials[_projectId].additionalPayeeSecondarySales;
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
/**
|
|
1405
|
+
* @notice View function returning Artist's additional payee secondary
|
|
1406
|
+
* sales percentage, for project `_projectId`.
|
|
1407
|
+
* @param _projectId Project ID to be queried.
|
|
1408
|
+
* @return uint256 Artist's additional payee secondary sales percentage.
|
|
1409
|
+
*/
|
|
1410
|
+
function projectIdToAdditionalPayeeSecondarySalesPercentage(
|
|
1411
|
+
uint256 _projectId
|
|
1412
|
+
) external view returns (uint256) {
|
|
1413
|
+
return
|
|
1414
|
+
projectIdToFinancials[_projectId]
|
|
1415
|
+
.additionalPayeeSecondarySalesPercentage;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
/**
|
|
1419
|
+
* @notice Returns project details for project `_projectId`.
|
|
1420
|
+
* @param _projectId Project to be queried.
|
|
1421
|
+
* @return projectName Name of project
|
|
1422
|
+
* @return artist Artist of project
|
|
1423
|
+
* @return description Project description
|
|
1424
|
+
* @return website Project website
|
|
1425
|
+
* @return license Project license
|
|
1426
|
+
* @dev this function was named projectDetails prior to V3 core contract.
|
|
1427
|
+
*/
|
|
1428
|
+
function projectDetails(
|
|
1429
|
+
uint256 _projectId
|
|
1430
|
+
)
|
|
1431
|
+
external
|
|
1432
|
+
view
|
|
1433
|
+
returns (
|
|
1434
|
+
string memory projectName,
|
|
1435
|
+
string memory artist,
|
|
1436
|
+
string memory description,
|
|
1437
|
+
string memory website,
|
|
1438
|
+
string memory license
|
|
1439
|
+
)
|
|
1440
|
+
{
|
|
1441
|
+
Project storage project = projects[_projectId];
|
|
1442
|
+
projectName = project.name;
|
|
1443
|
+
artist = project.artist;
|
|
1444
|
+
description = project.description;
|
|
1445
|
+
website = project.website;
|
|
1446
|
+
license = project.license;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
/**
|
|
1450
|
+
* @notice Returns project state data for project `_projectId`.
|
|
1451
|
+
* @param _projectId Project to be queried
|
|
1452
|
+
* @return invocations Current number of invocations
|
|
1453
|
+
* @return maxInvocations Maximum allowed invocations
|
|
1454
|
+
* @return active Boolean representing if project is currently active
|
|
1455
|
+
* @return paused Boolean representing if project is paused
|
|
1456
|
+
* @return completedTimestamp zero if project not complete, otherwise
|
|
1457
|
+
* timestamp of project completion.
|
|
1458
|
+
* @return locked Boolean representing if project is locked
|
|
1459
|
+
* @dev price and currency info are located on minter contracts
|
|
1460
|
+
*/
|
|
1461
|
+
function projectStateData(
|
|
1462
|
+
uint256 _projectId
|
|
1463
|
+
)
|
|
1464
|
+
external
|
|
1465
|
+
view
|
|
1466
|
+
returns (
|
|
1467
|
+
uint256 invocations,
|
|
1468
|
+
uint256 maxInvocations,
|
|
1469
|
+
bool active,
|
|
1470
|
+
bool paused,
|
|
1471
|
+
uint256 completedTimestamp,
|
|
1472
|
+
bool locked
|
|
1473
|
+
)
|
|
1474
|
+
{
|
|
1475
|
+
Project storage project = projects[_projectId];
|
|
1476
|
+
invocations = project.invocations;
|
|
1477
|
+
maxInvocations = project.maxInvocations;
|
|
1478
|
+
active = project.active;
|
|
1479
|
+
paused = project.paused;
|
|
1480
|
+
completedTimestamp = project.completedTimestamp;
|
|
1481
|
+
locked = !_projectUnlocked(_projectId);
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
/**
|
|
1485
|
+
* @notice Returns artist payment information for project `_projectId`.
|
|
1486
|
+
* @param _projectId Project to be queried
|
|
1487
|
+
* @return artistAddress Project Artist's address
|
|
1488
|
+
* @return additionalPayeePrimarySales Additional payee address for primary
|
|
1489
|
+
* sales
|
|
1490
|
+
* @return additionalPayeePrimarySalesPercentage Percentage of artist revenue
|
|
1491
|
+
* to be sent to the additional payee address for primary sales
|
|
1492
|
+
* @return additionalPayeeSecondarySales Additional payee address for secondary
|
|
1493
|
+
* sales royalties
|
|
1494
|
+
* @return additionalPayeeSecondarySalesPercentage Percentage of artist revenue
|
|
1495
|
+
* to be sent to the additional payee address for secondary sales royalties
|
|
1496
|
+
* @return secondaryMarketRoyaltyPercentage Royalty percentage to be sent to
|
|
1497
|
+
* combination of artist and additional payee. This does not include the
|
|
1498
|
+
* platform's percentage of secondary sales royalties, which is defined as
|
|
1499
|
+
* the sum of `renderProviderSecondarySalesBPS`
|
|
1500
|
+
* and `platformProviderSecondarySalesBPS`.
|
|
1501
|
+
*/
|
|
1502
|
+
function projectArtistPaymentInfo(
|
|
1503
|
+
uint256 _projectId
|
|
1504
|
+
)
|
|
1505
|
+
external
|
|
1506
|
+
view
|
|
1507
|
+
returns (
|
|
1508
|
+
address artistAddress,
|
|
1509
|
+
address additionalPayeePrimarySales,
|
|
1510
|
+
uint256 additionalPayeePrimarySalesPercentage,
|
|
1511
|
+
address additionalPayeeSecondarySales,
|
|
1512
|
+
uint256 additionalPayeeSecondarySalesPercentage,
|
|
1513
|
+
uint256 secondaryMarketRoyaltyPercentage
|
|
1514
|
+
)
|
|
1515
|
+
{
|
|
1516
|
+
ProjectFinance storage projectFinance = projectIdToFinancials[
|
|
1517
|
+
_projectId
|
|
1518
|
+
];
|
|
1519
|
+
artistAddress = projectFinance.artistAddress;
|
|
1520
|
+
additionalPayeePrimarySales = projectFinance
|
|
1521
|
+
.additionalPayeePrimarySales;
|
|
1522
|
+
additionalPayeePrimarySalesPercentage = projectFinance
|
|
1523
|
+
.additionalPayeePrimarySalesPercentage;
|
|
1524
|
+
additionalPayeeSecondarySales = projectFinance
|
|
1525
|
+
.additionalPayeeSecondarySales;
|
|
1526
|
+
additionalPayeeSecondarySalesPercentage = projectFinance
|
|
1527
|
+
.additionalPayeeSecondarySalesPercentage;
|
|
1528
|
+
secondaryMarketRoyaltyPercentage = projectFinance
|
|
1529
|
+
.secondaryMarketRoyaltyPercentage;
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
/**
|
|
1533
|
+
* @notice Returns script information for project `_projectId`.
|
|
1534
|
+
* @param _projectId Project to be queried.
|
|
1535
|
+
* @return scriptTypeAndVersion Project's script type and version
|
|
1536
|
+
* (e.g. "p5js(atSymbol)1.0.0")
|
|
1537
|
+
* @return aspectRatio Aspect ratio of project (e.g. "1" for square,
|
|
1538
|
+
* "1.77777778" for 16:9, etc.)
|
|
1539
|
+
* @return scriptCount Count of scripts for project
|
|
1540
|
+
*/
|
|
1541
|
+
function projectScriptDetails(
|
|
1542
|
+
uint256 _projectId
|
|
1543
|
+
)
|
|
1544
|
+
external
|
|
1545
|
+
view
|
|
1546
|
+
returns (
|
|
1547
|
+
string memory scriptTypeAndVersion,
|
|
1548
|
+
string memory aspectRatio,
|
|
1549
|
+
uint256 scriptCount
|
|
1550
|
+
)
|
|
1551
|
+
{
|
|
1552
|
+
Project storage project = projects[_projectId];
|
|
1553
|
+
scriptTypeAndVersion = project.scriptTypeAndVersion.toString();
|
|
1554
|
+
aspectRatio = project.aspectRatio;
|
|
1555
|
+
scriptCount = project.scriptCount;
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
/**
|
|
1559
|
+
* @notice Returns address with bytecode containing project script for
|
|
1560
|
+
* project `_projectId` at script index `_index`.
|
|
1561
|
+
*/
|
|
1562
|
+
function projectScriptBytecodeAddressByIndex(
|
|
1563
|
+
uint256 _projectId,
|
|
1564
|
+
uint256 _index
|
|
1565
|
+
) external view returns (address) {
|
|
1566
|
+
return projects[_projectId].scriptBytecodeAddresses[_index];
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
/**
|
|
1570
|
+
* @notice Returns script for project `_projectId` at script index `_index`.
|
|
1571
|
+
* @param _projectId Project to be queried.
|
|
1572
|
+
* @param _index Index of script to be queried.
|
|
1573
|
+
*/
|
|
1574
|
+
function projectScriptByIndex(
|
|
1575
|
+
uint256 _projectId,
|
|
1576
|
+
uint256 _index
|
|
1577
|
+
) external view returns (string memory) {
|
|
1578
|
+
Project storage project = projects[_projectId];
|
|
1579
|
+
// If trying to access an out-of-index script, return the empty string.
|
|
1580
|
+
if (_index >= project.scriptCount) {
|
|
1581
|
+
return "";
|
|
1582
|
+
}
|
|
1583
|
+
return project.scriptBytecodeAddresses[_index].readFromBytecode();
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
/**
|
|
1587
|
+
* @notice Returns base URI for project `_projectId`.
|
|
1588
|
+
* @param _projectId Project to be queried.
|
|
1589
|
+
* @return projectBaseURI Base URI for project
|
|
1590
|
+
*/
|
|
1591
|
+
function projectURIInfo(
|
|
1592
|
+
uint256 _projectId
|
|
1593
|
+
) external view returns (string memory projectBaseURI) {
|
|
1594
|
+
projectBaseURI = projects[_projectId].projectBaseURI;
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
/**
|
|
1598
|
+
* @notice Backwards-compatible (pre-V3) function returning if `_minter` is
|
|
1599
|
+
* minterContract.
|
|
1600
|
+
* @param _minter Address to be queried.
|
|
1601
|
+
* @return bool Boolean representing if `_minter` is minterContract.
|
|
1602
|
+
*/
|
|
1603
|
+
function isMintWhitelisted(address _minter) external view returns (bool) {
|
|
1604
|
+
return (minterContract == _minter);
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
/**
|
|
1608
|
+
* @notice Gets qty of randomizers in history of all randomizers used by
|
|
1609
|
+
* this core contract. If a randomizer is switched away from then back to,
|
|
1610
|
+
* it will show up in the history twice.
|
|
1611
|
+
* @return randomizerHistoryCount Count of randomizers in history
|
|
1612
|
+
*/
|
|
1613
|
+
function numHistoricalRandomizers() external view returns (uint256) {
|
|
1614
|
+
return _historicalRandomizerAddresses.length;
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
/**
|
|
1618
|
+
* @notice Gets address of randomizer at index `_index` in history of all
|
|
1619
|
+
* randomizers used by this core contract. Index is zero-based.
|
|
1620
|
+
* @param _index Historical index of randomizer to be queried.
|
|
1621
|
+
* @return randomizerAddress Address of randomizer at index `_index`.
|
|
1622
|
+
* @dev If a randomizer is switched away from and then switched back to, it
|
|
1623
|
+
* will show up in the history twice.
|
|
1624
|
+
*/
|
|
1625
|
+
function getHistoricalRandomizerAt(
|
|
1626
|
+
uint256 _index
|
|
1627
|
+
) external view returns (address) {
|
|
1628
|
+
require(
|
|
1629
|
+
_index < _historicalRandomizerAddresses.length,
|
|
1630
|
+
"Index out of bounds"
|
|
1631
|
+
);
|
|
1632
|
+
return _historicalRandomizerAddresses[_index];
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
/**
|
|
1636
|
+
* @notice Gets royalty Basis Points (BPS) for token ID `_tokenId`.
|
|
1637
|
+
* This conforms to the IManifold interface designated in the Royalty
|
|
1638
|
+
* Registry's RoyaltyEngineV1.sol contract.
|
|
1639
|
+
* ref: https://github.com/manifoldxyz/royalty-registry-solidity
|
|
1640
|
+
* @param _tokenId Token ID to be queried.
|
|
1641
|
+
* @return recipients Array of royalty payment recipients
|
|
1642
|
+
* @return bps Array of Basis Points (BPS) allocated to each recipient,
|
|
1643
|
+
* aligned by index.
|
|
1644
|
+
* @dev reverts if invalid _tokenId
|
|
1645
|
+
* @dev only returns recipients that have a non-zero BPS allocation
|
|
1646
|
+
*/
|
|
1647
|
+
function getRoyalties(
|
|
1648
|
+
uint256 _tokenId
|
|
1649
|
+
)
|
|
1650
|
+
external
|
|
1651
|
+
view
|
|
1652
|
+
onlyValidTokenId(_tokenId)
|
|
1653
|
+
returns (address payable[] memory recipients, uint256[] memory bps)
|
|
1654
|
+
{
|
|
1655
|
+
// initialize arrays with maximum potential length
|
|
1656
|
+
recipients = new address payable[](4);
|
|
1657
|
+
bps = new uint256[](4);
|
|
1658
|
+
|
|
1659
|
+
uint256 projectId = tokenIdToProjectId(_tokenId);
|
|
1660
|
+
ProjectFinance storage projectFinance = projectIdToFinancials[
|
|
1661
|
+
projectId
|
|
1662
|
+
];
|
|
1663
|
+
// load values into memory
|
|
1664
|
+
uint256 royaltyPercentageForArtistAndAdditional = projectFinance
|
|
1665
|
+
.secondaryMarketRoyaltyPercentage;
|
|
1666
|
+
uint256 additionalPayeePercentage = projectFinance
|
|
1667
|
+
.additionalPayeeSecondarySalesPercentage;
|
|
1668
|
+
// calculate BPS = percentage * 100
|
|
1669
|
+
uint256 artistBPS = (ONE_HUNDRED - additionalPayeePercentage) *
|
|
1670
|
+
royaltyPercentageForArtistAndAdditional;
|
|
1671
|
+
|
|
1672
|
+
uint256 additionalBPS = additionalPayeePercentage *
|
|
1673
|
+
royaltyPercentageForArtistAndAdditional;
|
|
1674
|
+
uint256 renderProviderBPS = renderProviderSecondarySalesBPS;
|
|
1675
|
+
uint256 platformProviderBPS = platformProviderSecondarySalesBPS;
|
|
1676
|
+
// populate arrays
|
|
1677
|
+
uint256 payeeCount;
|
|
1678
|
+
if (artistBPS > 0) {
|
|
1679
|
+
recipients[payeeCount] = projectFinance.artistAddress;
|
|
1680
|
+
bps[payeeCount++] = artistBPS;
|
|
1681
|
+
}
|
|
1682
|
+
if (additionalBPS > 0) {
|
|
1683
|
+
recipients[payeeCount] = projectFinance
|
|
1684
|
+
.additionalPayeeSecondarySales;
|
|
1685
|
+
bps[payeeCount++] = additionalBPS;
|
|
1686
|
+
}
|
|
1687
|
+
if (renderProviderBPS > 0) {
|
|
1688
|
+
recipients[payeeCount] = renderProviderSecondarySalesAddress;
|
|
1689
|
+
bps[payeeCount++] = renderProviderBPS;
|
|
1690
|
+
}
|
|
1691
|
+
if (platformProviderBPS > 0) {
|
|
1692
|
+
recipients[payeeCount] = platformProviderSecondarySalesAddress;
|
|
1693
|
+
bps[payeeCount++] = platformProviderBPS;
|
|
1694
|
+
}
|
|
1695
|
+
// trim arrays if necessary
|
|
1696
|
+
if (4 > payeeCount) {
|
|
1697
|
+
assembly {
|
|
1698
|
+
let decrease := sub(4, payeeCount)
|
|
1699
|
+
mstore(recipients, sub(mload(recipients), decrease))
|
|
1700
|
+
mstore(bps, sub(mload(bps), decrease))
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
return (recipients, bps);
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
/**
|
|
1707
|
+
* @notice WARNING - DO NOT USE - THIS IS AN INTENTIONALLY BUGGED FUNCTION
|
|
1708
|
+
* @param _projectId Project ID to be queried.
|
|
1709
|
+
* @param _price Sale price of token.
|
|
1710
|
+
* @return renderProviderRevenue_ amount of revenue to be sent to the
|
|
1711
|
+
* render provider
|
|
1712
|
+
* @return renderProviderAddress_ address to send render provider revenue to
|
|
1713
|
+
* @return platformProviderRevenue_ amount of revenue to be sent to the
|
|
1714
|
+
* platform provider
|
|
1715
|
+
* @return platformProviderAddress_ address to send platform provider revenue to
|
|
1716
|
+
* @return artistRevenue_ amount of revenue to be sent to Artist
|
|
1717
|
+
* @return artistAddress_ address to send Artist revenue to. Will be null
|
|
1718
|
+
* if no revenue is due to artist (gas optimization).
|
|
1719
|
+
* @return additionalPayeePrimaryRevenue_ amount of revenue to be sent to
|
|
1720
|
+
* additional payee for primary sales
|
|
1721
|
+
* @dev this always returns four addresses and four revenues, but if the
|
|
1722
|
+
* revenue is zero, the corresponding address will be address(0). It is up
|
|
1723
|
+
* to the contract performing the revenue split to handle this
|
|
1724
|
+
* appropriately.
|
|
1725
|
+
*/
|
|
1726
|
+
function getPrimaryRevenueSplits(
|
|
1727
|
+
uint256 _projectId,
|
|
1728
|
+
uint256 _price
|
|
1729
|
+
)
|
|
1730
|
+
external
|
|
1731
|
+
view
|
|
1732
|
+
returns (
|
|
1733
|
+
uint256 renderProviderRevenue_,
|
|
1734
|
+
address payable renderProviderAddress_,
|
|
1735
|
+
uint256 platformProviderRevenue_,
|
|
1736
|
+
address payable platformProviderAddress_,
|
|
1737
|
+
uint256 artistRevenue_,
|
|
1738
|
+
address payable artistAddress_,
|
|
1739
|
+
uint256 additionalPayeePrimaryRevenue_
|
|
1740
|
+
)
|
|
1741
|
+
// intentionally returning 7 values to invoke failure during minter deployment
|
|
1742
|
+
{
|
|
1743
|
+
ProjectFinance storage projectFinance = projectIdToFinancials[
|
|
1744
|
+
_projectId
|
|
1745
|
+
];
|
|
1746
|
+
// calculate revenues – this is a three-way split between the
|
|
1747
|
+
// render provider, the platform provider, and the artist, and
|
|
1748
|
+
// is safe to perform this given that in the case of loss of
|
|
1749
|
+
// precision Solidity will round down.
|
|
1750
|
+
uint256 projectFunds = _price;
|
|
1751
|
+
renderProviderRevenue_ =
|
|
1752
|
+
(_price * uint256(_renderProviderPrimarySalesPercentage)) /
|
|
1753
|
+
ONE_HUNDRED;
|
|
1754
|
+
// renderProviderRevenue_ percentage is always <=100, so guaranteed to never underflow
|
|
1755
|
+
projectFunds -= renderProviderRevenue_;
|
|
1756
|
+
platformProviderRevenue_ =
|
|
1757
|
+
(_price * uint256(_platformProviderPrimarySalesPercentage)) /
|
|
1758
|
+
ONE_HUNDRED;
|
|
1759
|
+
// platformProviderRevenue_ percentage is always <=100, so guaranteed to never underflow
|
|
1760
|
+
projectFunds -= platformProviderRevenue_;
|
|
1761
|
+
additionalPayeePrimaryRevenue_ =
|
|
1762
|
+
(projectFunds *
|
|
1763
|
+
projectFinance.additionalPayeePrimarySalesPercentage) /
|
|
1764
|
+
ONE_HUNDRED;
|
|
1765
|
+
// projectIdToAdditionalPayeePrimarySalesPercentage is always
|
|
1766
|
+
// <=100, so guaranteed to never underflow
|
|
1767
|
+
artistRevenue_ = projectFunds - additionalPayeePrimaryRevenue_;
|
|
1768
|
+
// set addresses from storage
|
|
1769
|
+
renderProviderAddress_ = renderProviderPrimarySalesAddress;
|
|
1770
|
+
platformProviderAddress_ = platformProviderPrimarySalesAddress;
|
|
1771
|
+
if (artistRevenue_ > 0) {
|
|
1772
|
+
artistAddress_ = projectFinance.artistAddress;
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
/**
|
|
1777
|
+
* @notice Backwards-compatible (pre-V3) getter returning contract admin
|
|
1778
|
+
* @return address Address of contract admin (same as owner)
|
|
1779
|
+
*/
|
|
1780
|
+
function admin() external view returns (address) {
|
|
1781
|
+
return owner();
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
/**
|
|
1785
|
+
* @notice Gets the project ID for a given `_tokenId`.
|
|
1786
|
+
* @param _tokenId Token ID to be queried.
|
|
1787
|
+
* @return _projectId Project ID for given `_tokenId`.
|
|
1788
|
+
*/
|
|
1789
|
+
function tokenIdToProjectId(
|
|
1790
|
+
uint256 _tokenId
|
|
1791
|
+
) public pure returns (uint256 _projectId) {
|
|
1792
|
+
return _tokenId / ONE_MILLION;
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
/**
|
|
1796
|
+
* @notice Convenience function that returns whether `_sender` is allowed
|
|
1797
|
+
* to call function with selector `_selector` on contract `_contract`, as
|
|
1798
|
+
* determined by this contract's current Admin ACL contract. Expected use
|
|
1799
|
+
* cases include minter contracts checking if caller is allowed to call
|
|
1800
|
+
* admin-gated functions on minter contracts.
|
|
1801
|
+
* @param _sender Address of the sender calling function with selector
|
|
1802
|
+
* `_selector` on contract `_contract`.
|
|
1803
|
+
* @param _contract Address of the contract being called by `_sender`.
|
|
1804
|
+
* @param _selector Function selector of the function being called by
|
|
1805
|
+
* `_sender`.
|
|
1806
|
+
* @return bool Whether `_sender` is allowed to call function with selector
|
|
1807
|
+
* `_selector` on contract `_contract`.
|
|
1808
|
+
* @dev assumes the Admin ACL contract is the owner of this contract, which
|
|
1809
|
+
* is expected to always be true.
|
|
1810
|
+
* @dev adminACLContract is expected to either be null address (if owner
|
|
1811
|
+
* has renounced ownership), or conform to IAdminACLV0 interface. Check for
|
|
1812
|
+
* null address first to avoid revert when admin has renounced ownership.
|
|
1813
|
+
*/
|
|
1814
|
+
function adminACLAllowed(
|
|
1815
|
+
address _sender,
|
|
1816
|
+
address _contract,
|
|
1817
|
+
bytes4 _selector
|
|
1818
|
+
) public returns (bool) {
|
|
1819
|
+
return
|
|
1820
|
+
owner() != address(0) &&
|
|
1821
|
+
adminACLContract.allowed(_sender, _contract, _selector);
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
/**
|
|
1825
|
+
* @notice Returns contract owner. Set to deployer's address by default on
|
|
1826
|
+
* contract deployment.
|
|
1827
|
+
* @return address Address of contract owner.
|
|
1828
|
+
* @dev ref: https://docs.openzeppelin.com/contracts/4.x/api/access#Ownable
|
|
1829
|
+
* @dev owner role was called `admin` prior to V3 core contract
|
|
1830
|
+
*/
|
|
1831
|
+
function owner() public view override(Ownable) returns (address) {
|
|
1832
|
+
return Ownable.owner();
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
/**
|
|
1836
|
+
* @notice Gets token URI for token ID `_tokenId`.
|
|
1837
|
+
* @param _tokenId Token ID to be queried.
|
|
1838
|
+
* @return string URI of token ID `_tokenId`.
|
|
1839
|
+
* @dev token URIs are the concatenation of the project base URI and the
|
|
1840
|
+
* token ID.
|
|
1841
|
+
*/
|
|
1842
|
+
function tokenURI(
|
|
1843
|
+
uint256 _tokenId
|
|
1844
|
+
) public view override onlyValidTokenId(_tokenId) returns (string memory) {
|
|
1845
|
+
string memory _projectBaseURI = projects[tokenIdToProjectId(_tokenId)]
|
|
1846
|
+
.projectBaseURI;
|
|
1847
|
+
return string.concat(_projectBaseURI, _tokenId.toString());
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
/**
|
|
1851
|
+
* @dev See {IERC165-supportsInterface}.
|
|
1852
|
+
*/
|
|
1853
|
+
function supportsInterface(
|
|
1854
|
+
bytes4 interfaceId
|
|
1855
|
+
) public view virtual override returns (bool) {
|
|
1856
|
+
return
|
|
1857
|
+
interfaceId == type(IManifold).interfaceId ||
|
|
1858
|
+
super.supportsInterface(interfaceId);
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
/**
|
|
1862
|
+
* @notice Forbids new projects from being created
|
|
1863
|
+
* @dev only performs operation and emits event if contract is not already
|
|
1864
|
+
* forbidding new projects.
|
|
1865
|
+
*/
|
|
1866
|
+
function _forbidNewProjects() internal {
|
|
1867
|
+
if (!newProjectsForbidden) {
|
|
1868
|
+
newProjectsForbidden = true;
|
|
1869
|
+
// emit PlatformUpdated(FIELD_NEW_PROJECTS_FORBIDDEN);
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
/**
|
|
1874
|
+
* @notice Transfers ownership of the contract to a new account (`newOwner`).
|
|
1875
|
+
* Internal function without access restriction.
|
|
1876
|
+
* @param newOwner New owner.
|
|
1877
|
+
* @dev owner role was called `admin` prior to V3 core contract.
|
|
1878
|
+
* @dev Overrides and wraps OpenZeppelin's _transferOwnership function to
|
|
1879
|
+
* also update adminACLContract for improved introspection.
|
|
1880
|
+
*/
|
|
1881
|
+
function _transferOwnership(address newOwner) internal override {
|
|
1882
|
+
Ownable._transferOwnership(newOwner);
|
|
1883
|
+
adminACLContract = IAdminACLV0(newOwner);
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
/**
|
|
1887
|
+
* @notice Updates sales addresses for the platform and render providers to
|
|
1888
|
+
* the input parameters.
|
|
1889
|
+
* @param _renderProviderPrimarySalesAddress Address of new primary sales
|
|
1890
|
+
* payment address.
|
|
1891
|
+
* @param _renderProviderSecondarySalesAddress Address of new secondary sales
|
|
1892
|
+
* payment address.
|
|
1893
|
+
* @param _platformProviderPrimarySalesAddress Address of new primary sales
|
|
1894
|
+
* payment address.
|
|
1895
|
+
* @param _platformProviderSecondarySalesAddress Address of new secondary sales
|
|
1896
|
+
* payment address.
|
|
1897
|
+
* @dev Note that this method does not check that the input address is
|
|
1898
|
+
* not `address(0)`, as it is expected that callers of this method should
|
|
1899
|
+
* perform input validation where applicable.
|
|
1900
|
+
*/
|
|
1901
|
+
function _updateProviderSalesAddresses(
|
|
1902
|
+
address _renderProviderPrimarySalesAddress,
|
|
1903
|
+
address _renderProviderSecondarySalesAddress,
|
|
1904
|
+
address _platformProviderPrimarySalesAddress,
|
|
1905
|
+
address _platformProviderSecondarySalesAddress
|
|
1906
|
+
) internal {
|
|
1907
|
+
platformProviderPrimarySalesAddress = payable(
|
|
1908
|
+
_platformProviderPrimarySalesAddress
|
|
1909
|
+
);
|
|
1910
|
+
platformProviderSecondarySalesAddress = payable(
|
|
1911
|
+
_platformProviderSecondarySalesAddress
|
|
1912
|
+
);
|
|
1913
|
+
renderProviderPrimarySalesAddress = payable(
|
|
1914
|
+
_renderProviderPrimarySalesAddress
|
|
1915
|
+
);
|
|
1916
|
+
renderProviderSecondarySalesAddress = payable(
|
|
1917
|
+
_renderProviderSecondarySalesAddress
|
|
1918
|
+
);
|
|
1919
|
+
// emit PlatformUpdated(FIELD_PROVIDER_SALES_ADDRESSES);
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
/**
|
|
1923
|
+
* @notice Updates randomizer address to `_randomizerAddress`.
|
|
1924
|
+
* @param _randomizerAddress New randomizer address.
|
|
1925
|
+
* @dev Note that this method does not check that the input address is
|
|
1926
|
+
* not `address(0)`, as it is expected that callers of this method should
|
|
1927
|
+
* perform input validation where applicable.
|
|
1928
|
+
*/
|
|
1929
|
+
function _updateRandomizerAddress(address _randomizerAddress) internal {
|
|
1930
|
+
randomizerContract = IRandomizerV2(_randomizerAddress);
|
|
1931
|
+
// populate historical randomizer array
|
|
1932
|
+
_historicalRandomizerAddresses.push(_randomizerAddress);
|
|
1933
|
+
// emit PlatformUpdated(FIELD_RANDOMIZER_ADDRESS);
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
/**
|
|
1937
|
+
* @notice Updates default base URI to `_defaultBaseURI`.
|
|
1938
|
+
* When new projects are added, their `projectBaseURI` is automatically
|
|
1939
|
+
* initialized to `_defaultBaseURI`.
|
|
1940
|
+
* @param _defaultBaseURI New default base URI.
|
|
1941
|
+
* @dev Note that this method does not check that the input string is not
|
|
1942
|
+
* the empty string, as it is expected that callers of this method should
|
|
1943
|
+
* perform input validation where applicable.
|
|
1944
|
+
*/
|
|
1945
|
+
function _updateDefaultBaseURI(string memory _defaultBaseURI) internal {
|
|
1946
|
+
defaultBaseURI = _defaultBaseURI;
|
|
1947
|
+
// emit PlatformUpdated(FIELD_DEFAULT_BASE_URI);
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
/**
|
|
1951
|
+
* @notice Internal function to complete a project.
|
|
1952
|
+
* @param _projectId Project ID to be completed.
|
|
1953
|
+
*/
|
|
1954
|
+
function _completeProject(uint256 _projectId) internal {
|
|
1955
|
+
projects[_projectId].completedTimestamp = uint64(block.timestamp);
|
|
1956
|
+
// emit ProjectUpdated(_projectId, FIELD_PROJECT_COMPLETED);
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
/**
|
|
1960
|
+
* @notice Internal function that returns whether a project is unlocked.
|
|
1961
|
+
* Projects automatically lock four weeks after they are completed.
|
|
1962
|
+
* Projects are considered completed when they have been invoked the
|
|
1963
|
+
* maximum number of times.
|
|
1964
|
+
* @param _projectId Project ID to be queried.
|
|
1965
|
+
* @return bool true if project is unlocked, false otherwise.
|
|
1966
|
+
* @dev This also enforces that the `_projectId` passed in is valid.
|
|
1967
|
+
*/
|
|
1968
|
+
function _projectUnlocked(
|
|
1969
|
+
uint256 _projectId
|
|
1970
|
+
) internal view onlyValidProjectId(_projectId) returns (bool) {
|
|
1971
|
+
uint256 projectCompletedTimestamp = projects[_projectId]
|
|
1972
|
+
.completedTimestamp;
|
|
1973
|
+
bool projectOpen = projectCompletedTimestamp == 0;
|
|
1974
|
+
return
|
|
1975
|
+
projectOpen ||
|
|
1976
|
+
(block.timestamp - projectCompletedTimestamp <
|
|
1977
|
+
FOUR_WEEKS_IN_SECONDS);
|
|
1978
|
+
}
|
|
1979
|
+
}
|