@artblocks/contracts 1.0.4 → 1.1.0

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.
Files changed (120) hide show
  1. package/README.md +2 -2
  2. package/artifacts/contracts/AdminACLV1.sol/AdminACLV1.json +2 -2
  3. package/artifacts/contracts/DependencyRegistryV0.sol/DependencyRegistryV0.json +97 -10
  4. package/artifacts/contracts/GenArt721CoreV3.sol/GenArt721CoreV3.json +4 -4
  5. package/artifacts/contracts/engine/V3/GenArt721CoreV3_Engine.sol/GenArt721CoreV3_Engine.json +4 -9
  6. package/artifacts/contracts/engine/V3/GenArt721CoreV3_Engine_Flex.sol/GenArt721CoreV3_Engine_Flex.json +4 -9
  7. package/artifacts/contracts/engine/V3/forks/GenArt721CoreV3_Engine_Flex_PROOF.sol/GenArt721CoreV3_Engine_Flex_PROOF.json +4 -9
  8. package/artifacts/contracts/engine/V3/forks/PROHIBITION/GenArt721CoreV3_Engine_Flex_PROHIBITION.sol/GenArt721CoreV3_Engine_Flex_PROHIBITION.json +4 -9
  9. package/artifacts/contracts/engine-registry/CoreRegistryV1.sol/CoreRegistryV1.json +12 -12
  10. package/artifacts/contracts/explorations/GenArt721CoreV3_Explorations.sol/GenArt721CoreV3_Explorations.json +4 -4
  11. package/artifacts/contracts/interfaces/v0.8.x/ICoreRegistryV1.sol/ICoreRegistryV1.json +6 -6
  12. package/artifacts/contracts/interfaces/v0.8.x/IDependencyRegistryV0.sol/IDependencyRegistryV0.json +19 -0
  13. package/artifacts/contracts/interfaces/v0.8.x/IMinterFilterV1.sol/IMinterFilterV1.json +22 -22
  14. package/artifacts/contracts/interfaces/v0.8.x/ISharedMinterDAExpSettlementV0.sol/ISharedMinterDAExpSettlementV0.json +8 -101
  15. package/artifacts/contracts/interfaces/v0.8.x/ISharedMinterDAExpV0.sol/ISharedMinterDAExpV0.json +1 -57
  16. package/artifacts/contracts/interfaces/v0.8.x/ISharedMinterDALinV0.sol/ISharedMinterDALinV0.json +1 -57
  17. package/artifacts/contracts/interfaces/v0.8.x/ISharedMinterDAV0.sol/ISharedMinterDAV0.json +2 -21
  18. package/artifacts/contracts/interfaces/v0.8.x/ISharedMinterHolderERC20V0.sol/ISharedMinterHolderERC20V0.json +158 -0
  19. package/artifacts/contracts/interfaces/v0.8.x/ISharedMinterHolderV0.sol/ISharedMinterHolderV0.json +15 -90
  20. package/artifacts/contracts/interfaces/v0.8.x/ISharedMinterMerkleV0.sol/ISharedMinterMerkleV0.json +10 -36
  21. package/artifacts/contracts/interfaces/v0.8.x/ISharedMinterSEAV0.sol/ISharedMinterSEAV0.json +4 -236
  22. package/artifacts/contracts/interfaces/v0.8.x/ISharedMinterSimplePurchaseV0.sol/ISharedMinterSimplePurchaseV0.json +5 -5
  23. package/artifacts/contracts/interfaces/v0.8.x/ISharedMinterV0.sol/ISharedMinterV0.json +7 -423
  24. package/artifacts/contracts/interfaces/v0.8.x/ISharedRandomizerV0.sol/ISharedRandomizerV0.json +17 -17
  25. package/artifacts/contracts/libs/v0.8.x/ABHelpers.sol/ABHelpers.json +2 -2
  26. package/artifacts/contracts/libs/v0.8.x/AuthLib.sol/AuthLib.json +2 -2
  27. package/artifacts/contracts/libs/v0.8.x/Bytes32Strings.sol/Bytes32Strings.json +2 -2
  28. package/artifacts/contracts/libs/v0.8.x/minter-libs/DAExpLib.sol/DAExpLib.json +60 -3
  29. package/artifacts/contracts/libs/v0.8.x/minter-libs/DALib.sol/DALib.json +30 -0
  30. package/artifacts/contracts/libs/v0.8.x/minter-libs/DALinLib.sol/DALinLib.json +60 -3
  31. package/artifacts/contracts/libs/v0.8.x/minter-libs/GenericMinterEventsLib.sol/GenericMinterEventsLib.json +346 -0
  32. package/artifacts/contracts/libs/v0.8.x/minter-libs/MaxInvocationsLib.sol/MaxInvocationsLib.json +29 -3
  33. package/artifacts/contracts/libs/v0.8.x/minter-libs/MerkleLib.sol/MerkleLib.json +30 -3
  34. package/artifacts/contracts/libs/v0.8.x/minter-libs/PolyptychLib.sol/PolyptychLib.json +2 -2
  35. package/artifacts/contracts/libs/v0.8.x/minter-libs/SEALib.sol/SEALib.json +254 -3
  36. package/artifacts/contracts/libs/v0.8.x/minter-libs/SetPriceLib.sol/SetPriceLib.json +48 -3
  37. package/artifacts/contracts/libs/v0.8.x/minter-libs/SettlementExpLib.sol/SettlementExpLib.json +41 -3
  38. package/artifacts/contracts/libs/v0.8.x/minter-libs/SplitFundsLib.sol/SplitFundsLib.json +35 -3
  39. package/artifacts/contracts/libs/v0.8.x/minter-libs/TokenHolderLib.sol/TokenHolderLib.json +79 -3
  40. package/artifacts/contracts/minter-suite/MinterFilter/MinterFilterV1.sol/MinterFilterV1.json +2 -2
  41. package/artifacts/contracts/minter-suite/MinterFilter/MinterFilterV2.sol/MinterFilterV2.json +41 -41
  42. package/artifacts/contracts/minter-suite/Minters/MinterDAExpHolderV5.sol/MinterDAExpHolderV5.json +66 -632
  43. package/artifacts/contracts/minter-suite/Minters/MinterDAExpSettlementV2.sol/MinterDAExpSettlementV2.json +2 -20
  44. package/artifacts/contracts/minter-suite/Minters/MinterDAExpSettlementV3.sol/MinterDAExpSettlementV3.json +67 -594
  45. package/artifacts/contracts/minter-suite/Minters/MinterDAExpV5.sol/MinterDAExpV5.json +33 -524
  46. package/artifacts/contracts/minter-suite/Minters/MinterDALinHolderV5.sol/MinterDALinHolderV5.json +66 -632
  47. package/artifacts/contracts/minter-suite/Minters/MinterDALinV5.sol/MinterDALinV5.json +33 -524
  48. package/artifacts/contracts/minter-suite/Minters/MinterSEAV1.sol/MinterSEAV1.json +51 -717
  49. package/artifacts/contracts/minter-suite/Minters/MinterSetPriceERC20V5.sol/MinterSetPriceERC20V5.json +58 -454
  50. package/artifacts/contracts/minter-suite/Minters/MinterSetPriceHolderV5.sol/MinterSetPriceHolderV5.json +61 -552
  51. package/artifacts/contracts/minter-suite/Minters/MinterSetPriceMerkleV5.sol/MinterSetPriceMerkleV5.json +55 -497
  52. package/artifacts/contracts/minter-suite/Minters/MinterSetPricePolyptychERC20V5.sol/MinterSetPricePolyptychERC20V5.json +110 -571
  53. package/artifacts/contracts/minter-suite/Minters/MinterSetPricePolyptychV5.sol/MinterSetPricePolyptychV5.json +69 -560
  54. package/artifacts/contracts/minter-suite/Minters/MinterSetPriceV5.sol/MinterSetPriceV5.json +28 -444
  55. package/artifacts/contracts/mock/DummySharedMinter.sol/DummySharedMinter.json +2 -2
  56. package/artifacts/contracts/mock/GenArt721CoreV3_Engine_IncorrectCoreType.sol/GenArt721CoreV3_Engine_IncorrectCoreType.json +4 -9
  57. package/artifacts/contracts/mock/OriginRegisterMock.sol/OriginRegisterMock.json +2 -2
  58. package/artifacts/contracts/mock/ReentrancyHolderMockShared.sol/ReentrancyHolderMockShared.json +136 -0
  59. package/artifacts/contracts/mock/ReentrancyMerkleMockShared.sol/ReentrancyMerkleMockShared.json +105 -0
  60. package/artifacts/contracts/mock/ReentrancyMockShared.sol/ReentrancyMockShared.json +100 -0
  61. package/artifacts/contracts/mock/ReentrancySEAMockShared.sol/ReentrancySEAAutoBidderMockShared.json +69 -0
  62. package/artifacts/contracts/randomizer/shared/SharedRandomizerV0.sol/SharedRandomizerV0.json +19 -19
  63. package/contracts/DependencyRegistryV0.sol +84 -16
  64. package/contracts/archive/readme.md +16 -0
  65. package/contracts/engine/README.md +21 -0
  66. package/contracts/engine/V3/GenArt721CoreV3_Engine.sol +4 -10
  67. package/contracts/engine/V3/GenArt721CoreV3_Engine_Flex.sol +4 -10
  68. package/contracts/engine/V3/forks/GenArt721CoreV3_Engine_Flex_PROOF.sol +4 -10
  69. package/contracts/engine/V3/forks/PROHIBITION/GenArt721CoreV3_Engine_Flex_PROHIBITION.sol +4 -10
  70. package/contracts/engine-registry/CoreRegistryV1.sol +58 -65
  71. package/contracts/interfaces/v0.8.x/ICoreRegistryV1.sol +6 -8
  72. package/contracts/interfaces/v0.8.x/IDependencyRegistryV0.sol +3 -0
  73. package/contracts/interfaces/v0.8.x/IMinterFilterV1.sol +25 -25
  74. package/contracts/interfaces/v0.8.x/ISharedMinterDAExpSettlementV0.sol +11 -20
  75. package/contracts/interfaces/v0.8.x/ISharedMinterDAExpV0.sol +1 -14
  76. package/contracts/interfaces/v0.8.x/ISharedMinterDALinV0.sol +1 -15
  77. package/contracts/interfaces/v0.8.x/ISharedMinterDAV0.sol +2 -8
  78. package/contracts/interfaces/v0.8.x/ISharedMinterHolderERC20V0.sol +47 -0
  79. package/contracts/interfaces/v0.8.x/ISharedMinterHolderV0.sol +15 -43
  80. package/contracts/interfaces/v0.8.x/ISharedMinterMerkleV0.sol +10 -20
  81. package/contracts/interfaces/v0.8.x/ISharedMinterSEAV0.sol +4 -67
  82. package/contracts/interfaces/v0.8.x/ISharedMinterSimplePurchaseV0.sol +5 -5
  83. package/contracts/interfaces/v0.8.x/ISharedMinterV0.sol +7 -155
  84. package/contracts/interfaces/v0.8.x/ISharedRandomizerV0.sol +37 -37
  85. package/contracts/libs/v0.8.x/ABHelpers.sol +14 -14
  86. package/contracts/libs/v0.8.x/AuthLib.sol +101 -102
  87. package/contracts/libs/v0.8.x/Bytes32Strings.sol +5 -5
  88. package/contracts/libs/v0.8.x/minter-libs/DAExpLib.sol +163 -38
  89. package/contracts/libs/v0.8.x/minter-libs/DALib.sol +26 -0
  90. package/contracts/libs/v0.8.x/minter-libs/DALinLib.sol +144 -37
  91. package/contracts/libs/v0.8.x/minter-libs/GenericMinterEventsLib.sol +171 -0
  92. package/contracts/libs/v0.8.x/minter-libs/MaxInvocationsLib.sol +250 -110
  93. package/contracts/libs/v0.8.x/minter-libs/MerkleLib.sol +257 -48
  94. package/contracts/libs/v0.8.x/minter-libs/PolyptychLib.sol +138 -58
  95. package/contracts/libs/v0.8.x/minter-libs/SEALib.sol +778 -103
  96. package/contracts/libs/v0.8.x/minter-libs/SetPriceLib.sol +135 -13
  97. package/contracts/libs/v0.8.x/minter-libs/SettlementExpLib.sol +567 -187
  98. package/contracts/libs/v0.8.x/minter-libs/SplitFundsLib.sol +480 -322
  99. package/contracts/libs/v0.8.x/minter-libs/TokenHolderLib.sol +214 -76
  100. package/contracts/minter-suite/MinterFilter/MinterFilterV2.sol +347 -304
  101. package/contracts/minter-suite/Minters/MinterDAExpHolderV5.sol +391 -491
  102. package/contracts/minter-suite/Minters/MinterDAExpSettlementV2.sol +1 -54
  103. package/contracts/minter-suite/Minters/MinterDAExpSettlementV3.sol +388 -628
  104. package/contracts/minter-suite/Minters/MinterDAExpV5.sol +212 -273
  105. package/contracts/minter-suite/Minters/MinterDALinHolderV5.sol +393 -488
  106. package/contracts/minter-suite/Minters/MinterDALinV5.sol +220 -277
  107. package/contracts/minter-suite/Minters/MinterSEAV1.sol +327 -664
  108. package/contracts/minter-suite/Minters/MinterSetPriceERC20V5.sol +278 -303
  109. package/contracts/minter-suite/Minters/MinterSetPriceHolderV5.sol +355 -446
  110. package/contracts/minter-suite/Minters/MinterSetPriceMerkleV5.sol +328 -416
  111. package/contracts/minter-suite/Minters/MinterSetPricePolyptychERC20V5.sol +538 -622
  112. package/contracts/minter-suite/Minters/MinterSetPricePolyptychV5.sol +429 -553
  113. package/contracts/minter-suite/Minters/MinterSetPriceV5.sol +180 -227
  114. package/contracts/mock/GenArt721CoreV3_Engine_IncorrectCoreType.sol +3 -10
  115. package/contracts/mock/ReentrancyHolderMockShared.sol +59 -0
  116. package/contracts/mock/ReentrancyMerkleMockShared.sol +58 -0
  117. package/contracts/mock/ReentrancyMockShared.sol +48 -0
  118. package/contracts/mock/ReentrancySEAMockShared.sol +40 -0
  119. package/contracts/randomizer/shared/SharedRandomizerV0.sol +78 -70
  120. package/package.json +39 -33
@@ -1,14 +1,14 @@
1
1
  // SPDX-License-Identifier: LGPL-3.0-only
2
2
  // Created By: Art Blocks Inc.
3
3
 
4
- import "../../../interfaces/v0.8.x/IMinterBaseV0.sol";
5
- import "../../../interfaces/v0.8.x/IGenArt721CoreContractV3_Base.sol";
6
- import "../../../interfaces/v0.8.x/IGenArt721CoreContractV3.sol";
7
- import "../../../interfaces/v0.8.x/IGenArt721CoreContractV3_Engine.sol";
4
+ pragma solidity ^0.8.0;
8
5
 
9
- import "@openzeppelin-4.7/contracts/token/ERC20/IERC20.sol";
6
+ import {IMinterBaseV0} from "../../../interfaces/v0.8.x/IMinterBaseV0.sol";
7
+ import {IGenArt721CoreContractV3_Base} from "../../../interfaces/v0.8.x/IGenArt721CoreContractV3_Base.sol";
8
+ import {IGenArt721CoreContractV3} from "../../../interfaces/v0.8.x/IGenArt721CoreContractV3.sol";
9
+ import {IGenArt721CoreContractV3_Engine} from "../../../interfaces/v0.8.x/IGenArt721CoreContractV3_Engine.sol";
10
10
 
11
- pragma solidity ^0.8.0;
11
+ import {IERC20} from "@openzeppelin-4.7/contracts/token/ERC20/IERC20.sol";
12
12
 
13
13
  /**
14
14
  * @title Art Blocks Split Funds Library
@@ -20,6 +20,26 @@ pragma solidity ^0.8.0;
20
20
  */
21
21
 
22
22
  library SplitFundsLib {
23
+ /**
24
+ * @notice Currency updated for project `projectId` to symbol
25
+ * `currencySymbol` and address `currencyAddress`.
26
+ * @param projectId Project ID currency was updated for
27
+ * @param coreContract Core contract address currency was updated for
28
+ * @param currencyAddress Currency address
29
+ * @param currencySymbol Currency symbol
30
+ */
31
+ event ProjectCurrencyInfoUpdated(
32
+ uint256 indexed projectId,
33
+ address indexed coreContract,
34
+ address indexed currencyAddress,
35
+ string currencySymbol
36
+ );
37
+
38
+ // position of Split Funds Lib storage, using a diamond storage pattern
39
+ // for this library
40
+ bytes32 constant SPLIT_FUNDS_LIB_STORAGE_POSITION =
41
+ keccak256("splitfundslib.storage");
42
+
23
43
  // contract-level variables
24
44
  struct IsEngineCache {
25
45
  bool isEngine;
@@ -32,10 +52,16 @@ library SplitFundsLib {
32
52
  string currencySymbol; // Assumed to be ETH if null
33
53
  }
34
54
 
55
+ // Diamond storage pattern is used in this library
56
+ struct SplitFundsLibStorage {
57
+ mapping(address coreContract => mapping(uint256 projectId => SplitFundsProjectConfig)) splitFundsProjectConfigs;
58
+ mapping(address coreContract => IsEngineCache) isEngineCacheConfigs;
59
+ }
60
+
35
61
  /**
36
62
  * @notice splits ETH funds between sender (if refund), providers,
37
63
  * artist, and artist's additional payee for a token purchased on
38
- * project `_projectId`.
64
+ * project `projectId`.
39
65
  * WARNING: This function uses msg.value and msg.sender to determine
40
66
  * refund amounts, and therefore may not be applicable to all use cases
41
67
  * (e.g. do not use with Dutch Auctions with on-chain settlement).
@@ -44,269 +70,281 @@ library SplitFundsLib {
44
70
  * @dev possible DoS during splits is acknowledged, and mitigated by
45
71
  * business practices, including end-to-end testing on mainnet, and
46
72
  * admin-accepted artist payment addresses.
47
- * @param _projectId Project ID for which funds shall be split.
48
- * @param _pricePerTokenInWei Current price of token, in Wei.
49
- * @param _coreContract Address of the GenArt721CoreContract associated
73
+ * @param projectId Project ID for which funds shall be split.
74
+ * @param pricePerTokenInWei Current price of token, in Wei.
75
+ * @param coreContract Address of the GenArt721CoreContract associated
50
76
  * with the project.
51
- * @param _isEngine Whether the core contract is an engine contract.
52
77
  */
53
- function splitFundsETH(
54
- uint256 _projectId,
55
- uint256 _pricePerTokenInWei,
56
- address _coreContract,
57
- bool _isEngine
78
+ function splitFundsETHRefundSender(
79
+ uint256 projectId,
80
+ uint256 pricePerTokenInWei,
81
+ address coreContract
58
82
  ) internal {
59
83
  if (msg.value > 0) {
60
- bool success_;
61
84
  // send refund to sender
62
- uint256 refund = msg.value - _pricePerTokenInWei;
85
+ uint256 refund = msg.value - pricePerTokenInWei;
63
86
  if (refund > 0) {
64
- (success_, ) = msg.sender.call{value: refund}("");
87
+ (bool success_, ) = msg.sender.call{value: refund}("");
65
88
  require(success_, "Refund failed");
66
89
  }
67
90
  // split revenues
68
- splitRevenuesETH(
69
- _projectId,
70
- _pricePerTokenInWei,
71
- _coreContract,
72
- _isEngine
73
- );
91
+ splitRevenuesETHNoRefund({
92
+ projectId: projectId,
93
+ valueInWei: pricePerTokenInWei,
94
+ coreContract: coreContract
95
+ });
74
96
  }
75
97
  }
76
98
 
77
99
  /**
78
100
  * @notice Splits ETH revenues between providers, artist, and artist's
79
- * additional payee for revenue generated by project `_projectId`.
101
+ * additional payee for revenue generated by project `projectId`.
102
+ * This function does NOT refund msg.sender, and does NOT use msg.value
103
+ * when determining the value to be split.
80
104
  * @dev possible DoS during splits is acknowledged, and mitigated by
81
105
  * business practices, including end-to-end testing on mainnet, and
82
106
  * admin-accepted artist payment addresses.
83
- * @param _projectId Project ID for which funds shall be split.
84
- * @param _valueInWei Value to be split, in Wei.
85
- * @param _coreContract Address of the GenArt721CoreContract
107
+ * @param projectId Project ID for which funds shall be split.
108
+ * @param valueInWei Value to be split, in Wei.
109
+ * @param coreContract Address of the GenArt721CoreContract
86
110
  * associated with the project.
87
- * @param _isEngine Whether the core contract is an engine contract.
88
111
  */
89
- function splitRevenuesETH(
90
- uint256 _projectId,
91
- uint256 _valueInWei,
92
- address _coreContract,
93
- bool _isEngine
112
+ function splitRevenuesETHNoRefund(
113
+ uint256 projectId,
114
+ uint256 valueInWei,
115
+ address coreContract
94
116
  ) internal {
95
- if (_valueInWei == 0) {
117
+ if (valueInWei == 0) {
96
118
  return; // return early
97
119
  }
98
- bool success;
99
120
  // split funds between platforms, artist, and artist's
100
121
  // additional payee
101
- uint256 renderProviderRevenue_;
102
- address payable renderProviderAddress_;
103
- uint256 artistRevenue_;
104
- address payable artistAddress_;
105
- uint256 additionalPayeePrimaryRevenue_;
106
- address payable additionalPayeePrimaryAddress_;
107
-
108
- if (_isEngine) {
122
+ bool isEngine_ = isEngine(coreContract);
123
+ uint256 renderProviderRevenue;
124
+ address payable renderProviderAddress;
125
+ uint256 platformProviderRevenue;
126
+ address payable platformProviderAddress;
127
+ uint256 artistRevenue;
128
+ address payable artistAddress;
129
+ uint256 additionalPayeePrimaryRevenue;
130
+ address payable additionalPayeePrimaryAddress;
131
+ if (isEngine_) {
109
132
  // get engine splits
110
- uint256 platformProviderRevenue_;
111
- address payable platformProviderAddress_;
112
133
  (
113
- renderProviderRevenue_,
114
- renderProviderAddress_,
115
- platformProviderRevenue_,
116
- platformProviderAddress_,
117
- artistRevenue_,
118
- artistAddress_,
119
- additionalPayeePrimaryRevenue_,
120
- additionalPayeePrimaryAddress_
121
- ) = IGenArt721CoreContractV3_Engine(_coreContract)
122
- .getPrimaryRevenueSplits(_projectId, _valueInWei);
123
- // Platform Provider payment (only possible if engine)
124
- if (platformProviderRevenue_ > 0) {
125
- (success, ) = platformProviderAddress_.call{
126
- value: platformProviderRevenue_
127
- }("");
128
- require(success, "Platform Provider payment failed");
129
- }
134
+ renderProviderRevenue,
135
+ renderProviderAddress,
136
+ platformProviderRevenue,
137
+ platformProviderAddress,
138
+ artistRevenue,
139
+ artistAddress,
140
+ additionalPayeePrimaryRevenue,
141
+ additionalPayeePrimaryAddress
142
+ ) = IGenArt721CoreContractV3_Engine(coreContract)
143
+ .getPrimaryRevenueSplits({
144
+ _projectId: projectId,
145
+ _price: valueInWei
146
+ });
130
147
  } else {
131
148
  // get flagship splits
149
+ // @dev note that platformProviderAddress and
150
+ // platformProviderRevenue remain 0 for flagship
132
151
  (
133
- renderProviderRevenue_, // artblocks revenue
134
- renderProviderAddress_, // artblocks address
135
- artistRevenue_,
136
- artistAddress_,
137
- additionalPayeePrimaryRevenue_,
138
- additionalPayeePrimaryAddress_
139
- ) = IGenArt721CoreContractV3(_coreContract).getPrimaryRevenueSplits(
140
- _projectId,
141
- _valueInWei
142
- );
143
- }
144
- // Render Provider / Art Blocks payment
145
- if (renderProviderRevenue_ > 0) {
146
- (success, ) = renderProviderAddress_.call{
147
- value: renderProviderRevenue_
148
- }("");
149
- require(success, "Render Provider payment failed");
150
- }
151
- // artist payment
152
- if (artistRevenue_ > 0) {
153
- (success, ) = artistAddress_.call{value: artistRevenue_}("");
154
- require(success, "Artist payment failed");
155
- }
156
- // additional payee payment
157
- if (additionalPayeePrimaryRevenue_ > 0) {
158
- (success, ) = additionalPayeePrimaryAddress_.call{
159
- value: additionalPayeePrimaryRevenue_
160
- }("");
161
- require(success, "Additional Payee payment failed");
152
+ renderProviderRevenue, // artblocks revenue
153
+ renderProviderAddress, // artblocks address
154
+ artistRevenue,
155
+ artistAddress,
156
+ additionalPayeePrimaryRevenue,
157
+ additionalPayeePrimaryAddress
158
+ ) = IGenArt721CoreContractV3(coreContract).getPrimaryRevenueSplits({
159
+ _projectId: projectId,
160
+ _price: valueInWei
161
+ });
162
162
  }
163
+ // require total revenue split is 100%
164
+ // @dev note that platformProviderRevenue remains 0 for flagship
165
+ require(
166
+ renderProviderRevenue +
167
+ platformProviderRevenue +
168
+ artistRevenue +
169
+ additionalPayeePrimaryRevenue ==
170
+ valueInWei,
171
+ "Invalid revenue split totals"
172
+ );
173
+ // distribute revenues
174
+ // @dev note that platformProviderAddress and platformProviderRevenue
175
+ // remain 0 for flagship
176
+ _sendPaymentsETH({
177
+ platformProviderRevenue: platformProviderRevenue,
178
+ platformProviderAddress: platformProviderAddress,
179
+ renderProviderRevenue: renderProviderRevenue,
180
+ renderProviderAddress: renderProviderAddress,
181
+ artistRevenue: artistRevenue,
182
+ artistAddress: artistAddress,
183
+ additionalPayeePrimaryRevenue: additionalPayeePrimaryRevenue,
184
+ additionalPayeePrimaryAddress: additionalPayeePrimaryAddress
185
+ });
163
186
  }
164
187
 
165
188
  /**
166
189
  * @notice Splits ERC20 funds between providers, artist, and artist's
167
- * additional payee, for a token purchased on project `_projectId`.
190
+ * additional payee, for a token purchased on project `projectId`.
168
191
  * The function performs checks to ensure that the ERC20 token is
169
192
  * approved for transfer, and that a non-zero ERC20 token address is
170
193
  * configured.
171
- * @dev This function relies on msg.sender and msg.value, so it must be
194
+ * @dev This function relies on msg.sender, so it must be
172
195
  * called directly from the contract that is receiving the payment.
173
196
  * @dev possible DoS during splits is acknowledged, and mitigated by
174
197
  * business practices, including end-to-end testing on mainnet, and
175
198
  * admin-accepted artist payment addresses.
176
- * @param _splitFundsProjectConfig SplitFundsProjectConfig of the project.
177
- * @param _projectId Project ID for which funds shall be split.
178
- * @param _pricePerTokenInWei Current price of token, in Wei.
179
- * @param _coreContract Core contract address.
180
- * @param _isEngine Whether the core contract is an engine contract.
199
+ * @param projectId Project ID for which funds shall be split.
200
+ * @param pricePerToken Current price of token, no decimals.
201
+ * @param coreContract Core contract address.
181
202
  */
182
203
  function splitFundsERC20(
183
- SplitFundsProjectConfig storage _splitFundsProjectConfig,
184
- uint256 _projectId,
185
- uint256 _pricePerTokenInWei,
186
- address _coreContract,
187
- bool _isEngine
204
+ uint256 projectId,
205
+ uint256 pricePerToken,
206
+ address coreContract
188
207
  ) internal {
189
- if (_pricePerTokenInWei == 0) {
208
+ if (pricePerToken == 0) {
190
209
  return; // nothing to split, return early
191
210
  }
192
- address currencyAddress = _splitFundsProjectConfig.currencyAddress;
193
- require(currencyAddress != address(0), "ERC20: payment not configured");
194
- require(msg.value == 0, "ERC20: No ETH when using ERC20");
195
- // ERC20 token is used for payment
196
- validateERC20Approvals({
197
- _msgSender: msg.sender,
198
- _currencyAddress: currencyAddress,
199
- _pricePerTokenInWei: _pricePerTokenInWei
200
- });
201
- IERC20 _projectCurrency = IERC20(currencyAddress);
211
+ IERC20 projectCurrency;
212
+ // block scope to avoid stack too deep error
213
+ {
214
+ SplitFundsProjectConfig
215
+ storage splitFundsProjectConfig = getSplitFundsProjectConfig({
216
+ projectId: projectId,
217
+ coreContract: coreContract
218
+ });
219
+ address currencyAddress = splitFundsProjectConfig.currencyAddress;
220
+ require(
221
+ currencyAddress != address(0),
222
+ "ERC20: payment not configured"
223
+ );
224
+ require(msg.value == 0, "ERC20: No ETH when using ERC20");
225
+ // ERC20 token is used for payment
226
+ validateERC20Approvals({
227
+ msgSender: msg.sender,
228
+ currencyAddress: currencyAddress,
229
+ pricePerToken: pricePerToken
230
+ });
231
+ projectCurrency = IERC20(currencyAddress);
232
+ }
202
233
  // split remaining funds between foundation, artist, and artist's
203
- // additional payee
204
- uint256 renderProviderRevenue_;
205
- address payable renderProviderAddress_;
206
- uint256 artistRevenue_;
207
- address payable artistAddress_;
208
- uint256 additionalPayeePrimaryRevenue_;
209
- address payable additionalPayeePrimaryAddress_;
210
-
211
- if (_isEngine) {
234
+ bool isEngine_ = isEngine(coreContract);
235
+ uint256 renderProviderRevenue;
236
+ address payable renderProviderAddress;
237
+ uint256 platformProviderRevenue;
238
+ address payable platformProviderAddress;
239
+ uint256 artistRevenue;
240
+ address payable artistAddress;
241
+ uint256 additionalPayeePrimaryRevenue;
242
+ address payable additionalPayeePrimaryAddress;
243
+ if (isEngine_) {
212
244
  // get engine splits
213
- uint256 platformProviderRevenue_;
214
- address payable platformProviderAddress_;
215
245
  (
216
- renderProviderRevenue_,
217
- renderProviderAddress_,
218
- platformProviderRevenue_,
219
- platformProviderAddress_,
220
- artistRevenue_,
221
- artistAddress_,
222
- additionalPayeePrimaryRevenue_,
223
- additionalPayeePrimaryAddress_
224
- ) = IGenArt721CoreContractV3_Engine(_coreContract)
225
- .getPrimaryRevenueSplits(_projectId, _pricePerTokenInWei);
226
- // Platform Provider payment (only possible if engine)
227
- if (platformProviderRevenue_ > 0) {
228
- require(
229
- _projectCurrency.transferFrom(
230
- msg.sender,
231
- platformProviderAddress_,
232
- platformProviderRevenue_
233
- ),
234
- "Platform Provider payment failed"
235
- );
236
- }
246
+ renderProviderRevenue,
247
+ renderProviderAddress,
248
+ platformProviderRevenue,
249
+ platformProviderAddress,
250
+ artistRevenue,
251
+ artistAddress,
252
+ additionalPayeePrimaryRevenue,
253
+ additionalPayeePrimaryAddress
254
+ ) = IGenArt721CoreContractV3_Engine(coreContract)
255
+ .getPrimaryRevenueSplits({
256
+ _projectId: projectId,
257
+ _price: pricePerToken
258
+ });
237
259
  } else {
238
260
  // get flagship splits
261
+ // @dev note that platformProviderAddress and
262
+ // platformProviderRevenue remain 0 for flagship
239
263
  (
240
- renderProviderRevenue_, // artblocks revenue
241
- renderProviderAddress_, // artblocks address
242
- artistRevenue_,
243
- artistAddress_,
244
- additionalPayeePrimaryRevenue_,
245
- additionalPayeePrimaryAddress_
246
- ) = IGenArt721CoreContractV3(_coreContract).getPrimaryRevenueSplits(
247
- _projectId,
248
- _pricePerTokenInWei
249
- );
250
- }
251
- // Art Blocks payment
252
- if (renderProviderRevenue_ > 0) {
253
- require(
254
- _projectCurrency.transferFrom(
255
- msg.sender,
256
- renderProviderAddress_,
257
- renderProviderRevenue_
258
- ),
259
- "Render Provider payment failed"
260
- );
261
- }
262
- // artist payment
263
- if (artistRevenue_ > 0) {
264
- require(
265
- _projectCurrency.transferFrom(
266
- msg.sender,
267
- artistAddress_,
268
- artistRevenue_
269
- ),
270
- "Artist payment failed"
271
- );
272
- }
273
- // additional payee payment
274
- if (additionalPayeePrimaryRevenue_ > 0) {
275
- // @dev some ERC20 may not revert on transfer failure, so we
276
- // check the return value
277
- require(
278
- _projectCurrency.transferFrom(
279
- msg.sender,
280
- additionalPayeePrimaryAddress_,
281
- additionalPayeePrimaryRevenue_
282
- ),
283
- "Additional Payee payment failed"
284
- );
264
+ renderProviderRevenue, // artblocks revenue
265
+ renderProviderAddress, // artblocks address
266
+ artistRevenue,
267
+ artistAddress,
268
+ additionalPayeePrimaryRevenue,
269
+ additionalPayeePrimaryAddress
270
+ ) = IGenArt721CoreContractV3(coreContract).getPrimaryRevenueSplits({
271
+ _projectId: projectId,
272
+ _price: pricePerToken
273
+ });
285
274
  }
275
+ // require total revenue split is 100%
276
+ // @dev note that platformProviderRevenue remains 0 for flagship
277
+ require(
278
+ renderProviderRevenue +
279
+ platformProviderRevenue +
280
+ artistRevenue +
281
+ additionalPayeePrimaryRevenue ==
282
+ pricePerToken,
283
+ "Invalid revenue split totals"
284
+ );
285
+ // distribute revenues
286
+ // @dev note that platformProviderAddress and platformProviderRevenue
287
+ // remain 0 for flagship
288
+ _sendPaymentsERC20({
289
+ projectCurrency: projectCurrency,
290
+ platformProviderRevenue: platformProviderRevenue,
291
+ platformProviderAddress: platformProviderAddress,
292
+ renderProviderRevenue: renderProviderRevenue,
293
+ renderProviderAddress: renderProviderAddress,
294
+ artistRevenue: artistRevenue,
295
+ artistAddress: artistAddress,
296
+ additionalPayeePrimaryRevenue: additionalPayeePrimaryRevenue,
297
+ additionalPayeePrimaryAddress: additionalPayeePrimaryAddress
298
+ });
286
299
  }
287
300
 
288
301
  /**
289
302
  * @notice Updates payment currency of the referenced
290
- * SplitFundsProjectConfig to be `_currencySymbol` at address
291
- * `_currencyAddress`.
303
+ * SplitFundsProjectConfig to be `currencySymbol` at address
304
+ * `currencyAddress`.
292
305
  * Only supports setting currency info of ERC20 tokens.
306
+ * Returns bool that is true if the price should be reset after this
307
+ * update. Price is recommended to be reset if the currency address was
308
+ * previously configured, but is now being updated to a different currency
309
+ * address. This is to protect accidental price reductions when changing
310
+ * currency if an artist is changing currencies in an unpaused state.
293
311
  * @dev artist-defined currency symbol is used instead of any on-chain
294
312
  * currency symbol.
295
- * @param _splitFundsProjectConfig SplitFundsProjectConfig to update.
296
- * @param _currencySymbol Currency symbol.
297
- * @param _currencyAddress Currency address.
313
+ * @param projectId Project ID to update.
314
+ * @param coreContract Core contract address.
315
+ * @param currencySymbol Currency symbol.
316
+ * @param currencyAddress Currency address.
317
+ * @return recommendPriceReset True if the price should be reset after this
318
+ * update.
298
319
  */
299
320
  function updateProjectCurrencyInfoERC20(
300
- SplitFundsProjectConfig storage _splitFundsProjectConfig,
301
- string memory _currencySymbol,
302
- address _currencyAddress
303
- ) internal {
321
+ uint256 projectId,
322
+ address coreContract,
323
+ string memory currencySymbol,
324
+ address currencyAddress
325
+ ) internal returns (bool recommendPriceReset) {
304
326
  // CHECKS
305
- require(_currencyAddress != address(0), "null address, only ERC20");
306
- require(bytes(_currencySymbol).length > 0, "only non-null symbol");
327
+ require(currencyAddress != address(0), "null address, only ERC20");
328
+ require(bytes(currencySymbol).length > 0, "only non-null symbol");
307
329
  // EFFECTS
308
- _splitFundsProjectConfig.currencySymbol = _currencySymbol;
309
- _splitFundsProjectConfig.currencyAddress = _currencyAddress;
330
+ SplitFundsProjectConfig
331
+ storage splitFundsProjectConfig = getSplitFundsProjectConfig({
332
+ projectId: projectId,
333
+ coreContract: coreContract
334
+ });
335
+ // recommend price reset if currency address was previously configured
336
+ if (splitFundsProjectConfig.currencyAddress != address(0)) {
337
+ recommendPriceReset = true;
338
+ }
339
+ splitFundsProjectConfig.currencySymbol = currencySymbol;
340
+ splitFundsProjectConfig.currencyAddress = currencyAddress;
341
+
342
+ emit ProjectCurrencyInfoUpdated({
343
+ projectId: projectId,
344
+ coreContract: coreContract,
345
+ currencyAddress: currencyAddress,
346
+ currencySymbol: currencySymbol
347
+ });
310
348
  }
311
349
 
312
350
  /**
@@ -316,8 +354,10 @@ library SplitFundsLib {
316
354
  * creating a temporary contract which uses `SELFDESTRUCT` to force send
317
355
  * the ETH.
318
356
  * Reverts if the current contract has insufficient balance.
319
- * @param _to The address to send ETH to.
320
- * @param _amount The amount of ETH to send.
357
+ * @param to The address to send ETH to.
358
+ * @param amount The amount of ETH to send.
359
+ * @param minterRefundGasLimit The gas limit to use when sending ETH, prior
360
+ * to fallback.
321
361
  * @dev This function is adapted from the `forceSafeTransferETH` function
322
362
  * in the `https://github.com/Vectorized/solady` repository, with
323
363
  * modifications to not check if the current contract has sufficient
@@ -326,9 +366,9 @@ library SplitFundsLib {
326
366
  * applicable.
327
367
  */
328
368
  function forceSafeTransferETH(
329
- address _to,
330
- uint256 _amount,
331
- uint256 _minterRefundGasLimit
369
+ address to,
370
+ uint256 amount,
371
+ uint256 minterRefundGasLimit
332
372
  ) internal {
333
373
  // Manually inlined because the compiler doesn't inline functions with
334
374
  // branches.
@@ -338,7 +378,7 @@ library SplitFundsLib {
338
378
  // balance, because that is not intended to be a valid state.
339
379
 
340
380
  // Transfer the ETH and check if it succeeded or not.
341
- if iszero(call(_minterRefundGasLimit, _to, _amount, 0, 0, 0, 0)) {
381
+ if iszero(call(minterRefundGasLimit, to, amount, 0, 0, 0, 0)) {
342
382
  // if the transfer failed, we create a temporary contract with
343
383
  // initialization code that uses `SELFDESTRUCT` to force send
344
384
  // the ETH.
@@ -356,14 +396,14 @@ library SplitFundsLib {
356
396
  //---------------------------------------------------------------------------------------------------------------//
357
397
  // Store the address in scratch space, starting at 0x00, which begins the 20-byte address at 32-20=12 in memory
358
398
  // @dev use scratch space because we have enough space for simple creation code (less than 0x40 bytes)
359
- mstore(0x00, _to)
399
+ mstore(0x00, to)
360
400
  // store opcode PUSH20 immediately before the address, starting at 0x0b (11) in memory
361
401
  mstore8(0x0b, 0x73)
362
402
  // store opcode SELFDESTRUCT immediately after the address, starting at 0x20 (32) in memory
363
403
  mstore8(0x20, 0xff)
364
404
  // this will always succeed because the contract creation code is
365
405
  // valid, and the address is valid because it is a 20-byte value
366
- if iszero(create(_amount, 0x0b, 0x16)) {
406
+ if iszero(create(amount, 0x0b, 0x16)) {
367
407
  // @dev For better gas estimation.
368
408
  if iszero(gt(gas(), 1000000)) {
369
409
  revert(0, 0)
@@ -374,22 +414,23 @@ library SplitFundsLib {
374
414
  }
375
415
 
376
416
  /**
377
- * @notice Returns whether or not the provided address `_coreContract`
417
+ * @notice Returns whether or not the provided address `coreContract`
378
418
  * is an Art Blocks Engine core contract. Caches the result for future access.
379
- * @param _coreContract Address of the core contract to check.
380
- * @param _isEngineCache The storage pointer to the project's
381
- * SplitFundsProjectConfig struct.
419
+ * @param coreContract Address of the core contract to check.
382
420
  */
383
- function isEngine(
384
- address _coreContract,
385
- IsEngineCache storage _isEngineCache
386
- ) internal returns (bool) {
387
- if (_isEngineCache.isCached) {
388
- return _isEngineCache.isEngine;
389
- } else {
390
- bool _isEngine = getV3CoreIsEngine(_coreContract, _isEngineCache);
391
- return _isEngine;
421
+ function isEngine(address coreContract) internal returns (bool) {
422
+ IsEngineCache storage isEngineCache = getIsEngineCacheConfig(
423
+ coreContract
424
+ );
425
+ // check cache, return early if cached
426
+ if (isEngineCache.isCached) {
427
+ return isEngineCache.isEngine;
392
428
  }
429
+ // populate cache and return result
430
+ bool isEngine_ = getV3CoreIsEngineView(coreContract);
431
+ isEngineCache.isCached = true;
432
+ isEngineCache.isEngine = isEngine_;
433
+ return isEngine_;
393
434
  }
394
435
 
395
436
  /**
@@ -402,10 +443,10 @@ library SplitFundsLib {
402
443
  * either a flagship or engine core contract.
403
444
  * @dev this function uses the length of the return data (in bytes) to
404
445
  * determine whether the core is an engine or not.
405
- * @param _coreContract The address of the deployed core contract.
446
+ * @param coreContract The address of the deployed core contract.
406
447
  */
407
448
  function getV3CoreIsEngineView(
408
- address _coreContract
449
+ address coreContract
409
450
  ) internal view returns (bool) {
410
451
  // call getPrimaryRevenueSplits() on core contract
411
452
  bytes memory payload = abi.encodeWithSignature(
@@ -413,7 +454,7 @@ library SplitFundsLib {
413
454
  0,
414
455
  0
415
456
  );
416
- (bool success, bytes memory returnData) = _coreContract.staticcall(
457
+ (bool success, bytes memory returnData) = coreContract.staticcall(
417
458
  payload
418
459
  );
419
460
  require(success, "getPrimaryRevenueSplits() call failed");
@@ -444,140 +485,257 @@ library SplitFundsLib {
444
485
  }
445
486
 
446
487
  /**
447
- * Gets the currency address and symbol for the referenced
488
+ * @notice Gets the currency address and symbol for the referenced
448
489
  * SplitFundsProjectConfig.
449
490
  * Only supports ERC20 tokens - returns currencySymbol of `UNCONFIG` if
450
491
  * `currencyAddress` is zero.
451
- * @param _splitFundsProjectConfig SplitFundsProjectConfig to read
492
+ * @param projectId Project ID to get config for
493
+ * @param coreContract Core contract address to get config for
452
494
  * @return currencyAddress
453
495
  * @return currencySymbol
454
496
  */
455
497
  function getCurrencyInfoERC20(
456
- SplitFundsProjectConfig storage _splitFundsProjectConfig
498
+ uint256 projectId,
499
+ address coreContract
457
500
  )
458
501
  internal
459
502
  view
460
503
  returns (address currencyAddress, string memory currencySymbol)
461
504
  {
462
- currencyAddress = _splitFundsProjectConfig.currencyAddress;
505
+ SplitFundsProjectConfig
506
+ storage splitFundsProjectConfig = getSplitFundsProjectConfig({
507
+ projectId: projectId,
508
+ coreContract: coreContract
509
+ });
510
+ currencyAddress = splitFundsProjectConfig.currencyAddress;
463
511
  // default to "UNCONFIG" if project currency address is initial value
464
512
  currencySymbol = currencyAddress == address(0)
465
513
  ? "UNCONFIG"
466
- : _splitFundsProjectConfig.currencySymbol;
514
+ : splitFundsProjectConfig.currencySymbol;
467
515
  }
468
516
 
469
517
  /**
470
- * Get the balance of `_currencyAddress` ERC20 tokens for `_walletAddress`.
471
- * @param _currencyAddress ERC20 token address.
472
- * @param _walletAddress wallet address.
518
+ * @notice Gets the balance of `currencyAddress` ERC20 tokens for `walletAddress`.
519
+ * @param currencyAddress ERC20 token address.
520
+ * @param walletAddress wallet address.
473
521
  * @return balance
474
522
  */
475
523
  function getERC20Balance(
476
- address _currencyAddress,
477
- address _walletAddress
524
+ address currencyAddress,
525
+ address walletAddress
478
526
  ) internal view returns (uint256) {
479
- return IERC20(_currencyAddress).balanceOf(_walletAddress);
527
+ return IERC20(currencyAddress).balanceOf(walletAddress);
480
528
  }
481
529
 
482
530
  /**
483
- * Gets the allowance of `_spenderAddress` to spend `_walletAddress`'s
484
- * `_currencyAddress` ERC20 tokens.
485
- * @param _currencyAddress ERC20 token address.
486
- * @param _walletAddress wallet address.
487
- * @param _spenderAddress spender address.
531
+ * @notice Gets the allowance of `spenderAddress` to spend `walletAddress`'s
532
+ * `currencyAddress` ERC20 tokens.
533
+ * @param currencyAddress ERC20 token address.
534
+ * @param walletAddress wallet address.
535
+ * @param spenderAddress spender address.
488
536
  * @return allowance
489
537
  */
490
538
  function getERC20Allowance(
491
- address _currencyAddress,
492
- address _walletAddress,
493
- address _spenderAddress
539
+ address currencyAddress,
540
+ address walletAddress,
541
+ address spenderAddress
494
542
  ) internal view returns (uint256 allowance) {
495
- allowance = IERC20(_currencyAddress).allowance(
496
- _walletAddress,
497
- _spenderAddress
498
- );
543
+ allowance = IERC20(currencyAddress).allowance({
544
+ owner: walletAddress,
545
+ spender: spenderAddress
546
+ });
499
547
  return allowance;
500
548
  }
501
549
 
502
550
  /**
503
- * @notice Returns whether a V3 core contract is an Art Blocks Engine
504
- * contract or not. Return value of false indicates that the core is a
505
- * flagship contract. If this function resolves without reverting, it also
506
- * updates the cache state for the given V3 core contract.
507
- * @dev this function reverts if a core contract does not return the
508
- * expected number of return values from getPrimaryRevenueSplits() for
509
- * either a flagship or engine core contract.
510
- * @dev this function uses the length of the return data (in bytes) to
511
- * determine whether the core is an engine or not.
512
- * @param _coreContract The address of the deployed core contract.
513
- * @param _isEngineCache The storage pointer to the project's
514
- * IsEngineCache struct.
515
- */
516
- function getV3CoreIsEngine(
517
- address _coreContract,
518
- IsEngineCache storage _isEngineCache
519
- ) private returns (bool) {
520
- // call getPrimaryRevenueSplits() on core contract
521
- bytes memory payload = abi.encodeWithSignature(
522
- "getPrimaryRevenueSplits(uint256,uint256)",
523
- 0,
524
- 0
525
- );
526
- (bool success, bytes memory returnData) = _coreContract.call(payload);
527
- require(success, "getPrimaryRevenueSplits() call failed");
528
- // determine whether core is engine or not, based on return data length
529
- uint256 returnDataLength = returnData.length;
530
- if (returnDataLength == 6 * 32) {
531
- // 6 32-byte words returned if flagship (not engine)
532
- // @dev 6 32-byte words are expected because the non-engine core
533
- // contracts return a payout address and uint256 payment value for
534
- // the artist, and artist's additional payee, and Art Blocks.
535
- // also note that per Solidity ABI encoding, the address return
536
- // values are padded to 32 bytes.
537
- _isEngineCache.isCached = true;
538
- _isEngineCache.isEngine = false;
539
- return false;
540
- } else if (returnDataLength == 8 * 32) {
541
- // 8 32-byte words returned if engine
542
- // @dev 8 32-byte words are expected because the engine core
543
- // contracts return a payout address and uint256 payment value for
544
- // the artist, artist's additional payee, render provider
545
- // typically Art Blocks, and platform provider (partner).
546
- // also note that per Solidity ABI encoding, the address return
547
- // values are padded to 32 bytes.
548
- _isEngineCache.isCached = true;
549
- _isEngineCache.isEngine = true;
550
- return true;
551
- } else {
552
- // unexpected return value length
553
- revert("Unexpected revenue split bytes");
554
- }
555
- }
556
-
557
- /**
558
- * Validate that `_msgSender` has approved the contract to spend at least
559
- * `_pricePerTokenInWei` of `_currencyAddress` ERC20 tokens, and that
560
- * `_msgSender` has a balance of at least `_pricePerTokenInWei` of
561
- * `_currencyAddress` ERC20 tokens.
551
+ * @notice Function validates that `msgSender` has approved the contract to spend at least
552
+ * `pricePerToken` of `currencyAddress` ERC20 tokens, and that
553
+ * `msgSender` has a balance of at least `pricePerToken` of
554
+ * `currencyAddress` ERC20 tokens.
562
555
  * Reverts if insufficient allowance or balance.
563
- * @param _msgSender Address of the message sender to validate.
564
- * @param _currencyAddress Address of the ERC20 token to validate.
565
- * @param _pricePerTokenInWei Price per token in wei to validate.
556
+ * @param msgSender Address of the message sender to validate.
557
+ * @param currencyAddress Address of the ERC20 token to validate.
558
+ * @param pricePerToken Price per token to validate (no decimals)
566
559
  */
567
560
  function validateERC20Approvals(
568
- address _msgSender,
569
- address _currencyAddress,
570
- uint256 _pricePerTokenInWei
561
+ address msgSender,
562
+ address currencyAddress,
563
+ uint256 pricePerToken
571
564
  ) private view {
572
565
  require(
573
- IERC20(_currencyAddress).allowance(_msgSender, address(this)) >=
574
- _pricePerTokenInWei,
566
+ IERC20(currencyAddress).allowance({
567
+ owner: msgSender,
568
+ spender: address(this)
569
+ }) >= pricePerToken,
575
570
  "Insufficient ERC20 allowance"
576
571
  );
577
572
  require(
578
- IERC20(_currencyAddress).balanceOf(_msgSender) >=
579
- _pricePerTokenInWei,
573
+ IERC20(currencyAddress).balanceOf(msgSender) >= pricePerToken,
580
574
  "Insufficient ERC20 balance"
581
575
  );
582
576
  }
577
+
578
+ /**
579
+ * @notice Sends ETH revenues between providers, artist, and artist's
580
+ * additional payee. Reverts if any payment fails.
581
+ * @param platformProviderRevenue Platform Provider revenue.
582
+ * @param platformProviderAddress Platform Provider address.
583
+ * @param renderProviderRevenue Render Provider revenue.
584
+ * @param renderProviderAddress Render Provider address.
585
+ * @param artistRevenue Artist revenue.
586
+ * @param artistAddress Artist address.
587
+ * @param additionalPayeePrimaryRevenue Additional Payee revenue.
588
+ * @param additionalPayeePrimaryAddress Additional Payee address.
589
+ */
590
+ function _sendPaymentsETH(
591
+ uint256 platformProviderRevenue,
592
+ address payable platformProviderAddress,
593
+ uint256 renderProviderRevenue,
594
+ address payable renderProviderAddress,
595
+ uint256 artistRevenue,
596
+ address payable artistAddress,
597
+ uint256 additionalPayeePrimaryRevenue,
598
+ address payable additionalPayeePrimaryAddress
599
+ ) private {
600
+ // Platform Provider payment (only possible if engine)
601
+ if (platformProviderRevenue > 0) {
602
+ (bool success, ) = platformProviderAddress.call{
603
+ value: platformProviderRevenue
604
+ }("");
605
+ require(success, "Platform Provider payment failed");
606
+ }
607
+ // Render Provider / Art Blocks payment
608
+ if (renderProviderRevenue > 0) {
609
+ (bool success, ) = renderProviderAddress.call{
610
+ value: renderProviderRevenue
611
+ }("");
612
+ require(success, "Render Provider payment failed");
613
+ }
614
+ // artist payment
615
+ if (artistRevenue > 0) {
616
+ (bool success, ) = artistAddress.call{value: artistRevenue}("");
617
+ require(success, "Artist payment failed");
618
+ }
619
+ // additional payee payment
620
+ if (additionalPayeePrimaryRevenue > 0) {
621
+ (bool success, ) = additionalPayeePrimaryAddress.call{
622
+ value: additionalPayeePrimaryRevenue
623
+ }("");
624
+ require(success, "Additional Payee payment failed");
625
+ }
626
+ }
627
+
628
+ /**
629
+ * @notice Sends ERC20 revenues between providers, artist, and artist's
630
+ * additional payee. Reverts if any payment fails. All revenue values
631
+ * should not use decimals.
632
+ * @dev This function relies on msg.sender, so it must be called from
633
+ * the contract that is receiving the payment.
634
+ * @param projectCurrency IERC20 payment token.
635
+ * @param platformProviderRevenue Platform Provider revenue.
636
+ * @param platformProviderAddress Platform Provider address.
637
+ * @param renderProviderRevenue Render Provider revenue.
638
+ * @param renderProviderAddress Render Provider address.
639
+ * @param artistRevenue Artist revenue.
640
+ * @param artistAddress Artist address.
641
+ * @param additionalPayeePrimaryRevenue Additional Payee revenue.
642
+ * @param additionalPayeePrimaryAddress Additional Payee address.
643
+ */
644
+ function _sendPaymentsERC20(
645
+ IERC20 projectCurrency,
646
+ uint256 platformProviderRevenue,
647
+ address payable platformProviderAddress,
648
+ uint256 renderProviderRevenue,
649
+ address payable renderProviderAddress,
650
+ uint256 artistRevenue,
651
+ address payable artistAddress,
652
+ uint256 additionalPayeePrimaryRevenue,
653
+ address payable additionalPayeePrimaryAddress
654
+ ) private {
655
+ // Platform Provider payment (only possible if engine)
656
+ if (platformProviderRevenue > 0) {
657
+ require(
658
+ projectCurrency.transferFrom({
659
+ from: msg.sender,
660
+ to: platformProviderAddress,
661
+ amount: platformProviderRevenue
662
+ }),
663
+ "Platform Provider payment failed"
664
+ );
665
+ }
666
+ // Art Blocks payment
667
+ if (renderProviderRevenue > 0) {
668
+ require(
669
+ projectCurrency.transferFrom({
670
+ from: msg.sender,
671
+ to: renderProviderAddress,
672
+ amount: renderProviderRevenue
673
+ }),
674
+ "Render Provider payment failed"
675
+ );
676
+ }
677
+ // artist payment
678
+ if (artistRevenue > 0) {
679
+ require(
680
+ projectCurrency.transferFrom({
681
+ from: msg.sender,
682
+ to: artistAddress,
683
+ amount: artistRevenue
684
+ }),
685
+ "Artist payment failed"
686
+ );
687
+ }
688
+ // additional payee payment
689
+ if (additionalPayeePrimaryRevenue > 0) {
690
+ // @dev some ERC20 may not revert on transfer failure, so we
691
+ // check the return value
692
+ require(
693
+ projectCurrency.transferFrom({
694
+ from: msg.sender,
695
+ to: additionalPayeePrimaryAddress,
696
+ amount: additionalPayeePrimaryRevenue
697
+ }),
698
+ "Additional Payee payment failed"
699
+ );
700
+ }
701
+ }
702
+
703
+ /**
704
+ * @notice Loads the SplitFundsProjectConfig for a given project and core
705
+ * contract.
706
+ * @param projectId Project Id to get config for
707
+ * @param coreContract Core contract address to get config for
708
+ */
709
+ function getSplitFundsProjectConfig(
710
+ uint256 projectId,
711
+ address coreContract
712
+ ) internal view returns (SplitFundsProjectConfig storage) {
713
+ return s().splitFundsProjectConfigs[coreContract][projectId];
714
+ }
715
+
716
+ /**
717
+ * @notice Loads the IsEngineCache for a given core contract.
718
+ * @param coreContract Core contract address to get config for
719
+ */
720
+ function getIsEngineCacheConfig(
721
+ address coreContract
722
+ ) internal view returns (IsEngineCache storage) {
723
+ return s().isEngineCacheConfigs[coreContract];
724
+ }
725
+
726
+ /**
727
+ * @notice Return the storage struct for reading and writing. This library
728
+ * uses a diamond storage pattern when managing storage.
729
+ * @return storageStruct The SetPriceLibStorage struct.
730
+ */
731
+ function s()
732
+ internal
733
+ pure
734
+ returns (SplitFundsLibStorage storage storageStruct)
735
+ {
736
+ bytes32 position = SPLIT_FUNDS_LIB_STORAGE_POSITION;
737
+ assembly ("memory-safe") {
738
+ storageStruct.slot := position
739
+ }
740
+ }
583
741
  }