@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
@@ -3,7 +3,14 @@
3
3
 
4
4
  pragma solidity ^0.8.0;
5
5
 
6
- import "@openzeppelin-4.7/contracts/utils/math/SafeCast.sol";
6
+ import {IMinterFilterV1} from "../../../interfaces/v0.8.x/IMinterFilterV1.sol";
7
+
8
+ import {ABHelpers} from "../ABHelpers.sol";
9
+ import {SplitFundsLib} from "./SplitFundsLib.sol";
10
+ import {MaxInvocationsLib} from "./MaxInvocationsLib.sol";
11
+
12
+ import {IERC721} from "@openzeppelin-4.7/contracts/token/ERC721/IERC721.sol";
13
+ import {SafeCast} from "@openzeppelin-4.7/contracts/utils/math/SafeCast.sol";
7
14
 
8
15
  /**
9
16
  * @title Art Blocks SEA Minter Library
@@ -14,6 +21,135 @@ import "@openzeppelin-4.7/contracts/utils/math/SafeCast.sol";
14
21
 
15
22
  library SEALib {
16
23
  using SafeCast for uint256;
24
+ /**
25
+ * @notice Minimum auction length, in seconds, was updated to be the
26
+ * provided value.
27
+ * @param minAuctionDurationSeconds Minimum auction length, in seconds
28
+ */
29
+ event MinAuctionDurationSecondsUpdated(uint256 minAuctionDurationSeconds);
30
+
31
+ /**
32
+ * @notice Admin-controlled time buffer updated
33
+ * @param minterTimeBufferSeconds Time buffer, in seconds
34
+ */
35
+ event MinterTimeBufferUpdated(uint32 minterTimeBufferSeconds);
36
+
37
+ /**
38
+ * @notice Admin-controlled refund gas limit updated
39
+ * @param refundGasLimit Gas limit to use when refunding the previous
40
+ * highest bidder, prior to using fallback force-send to refund
41
+ */
42
+ event MinterRefundGasLimitUpdated(uint24 refundGasLimit);
43
+
44
+ /**
45
+ * @notice Future auctions for project `projectId` on core contract
46
+ * `coreContract` configured.
47
+ * @param projectId Project Id configured
48
+ * @param coreContract Core contract address configured
49
+ * @param timestampStart Start timestamp after which future auctions may be
50
+ * started. If zero, auctions can start immediately.
51
+ * @param auctionDurationSeconds Duration of each new auction, before any
52
+ * extensions due to late bids
53
+ * @param basePrice Minimum bid price for the token auctions
54
+ * @param minBidIncrementPercentage Minimum bid increment percentage. Each
55
+ * subsequent bid must be at least this percentage greater than the
56
+ * previous bid. The value is expressed as a whole percentage, e.g. 5% is
57
+ * 5, 10% is 10, etc.
58
+ */
59
+ event ConfiguredFutureAuctions(
60
+ uint256 indexed projectId,
61
+ address indexed coreContract,
62
+ uint64 timestampStart,
63
+ uint32 auctionDurationSeconds,
64
+ uint256 basePrice,
65
+ uint8 minBidIncrementPercentage
66
+ );
67
+
68
+ /**
69
+ * @notice New token auction created, token created and sent to minter
70
+ * @param tokenId Token Id for which auction was created
71
+ * @param coreContract Core contract address for the given token
72
+ * @param bidder Bidder's address who initialized the auction
73
+ * @param bidAmount Bid amount
74
+ * @param endTime Auction end time
75
+ * @param minBidIncrementPercentage Minimum bid increment percentage. Each
76
+ * subsequent bid must be at least this percentage greater than the
77
+ * previous bid. The value is expressed as a whole percentage, e.g. 5% is
78
+ * 5, 10% is 10, etc.
79
+ */
80
+ event AuctionInitialized(
81
+ uint256 indexed tokenId,
82
+ address indexed coreContract,
83
+ address indexed bidder,
84
+ uint256 bidAmount,
85
+ uint64 endTime,
86
+ uint8 minBidIncrementPercentage
87
+ );
88
+
89
+ /**
90
+ * @notice Successful bid placed on token auction
91
+ * @param tokenId Token Id of auction bid on
92
+ * @param coreContract Core contract address for the given token
93
+ * @param bidder Bidder's address who placed the bid
94
+ * @param bidAmount Bid amount
95
+ */
96
+ event AuctionBid(
97
+ uint256 indexed tokenId,
98
+ address indexed coreContract,
99
+ address indexed bidder,
100
+ uint256 bidAmount
101
+ );
102
+
103
+ /**
104
+ * @notice Token auction was settled (token distributed to winner)
105
+ * @param tokenId Token Id of auction settled
106
+ * @param coreContract Core contract address for the given token
107
+ * @param winner Winner's address
108
+ * @param price Winning bid amount
109
+ */
110
+ event AuctionSettled(
111
+ uint256 indexed tokenId,
112
+ address indexed coreContract,
113
+ address indexed winner,
114
+ uint256 price
115
+ );
116
+
117
+ /**
118
+ * @notice Future auction details for project `projectId` reset
119
+ * @param projectId Project Id that was reset
120
+ * @param coreContract Core contract address that was reset
121
+ */
122
+ event ResetAuctionDetails(
123
+ uint256 indexed projectId,
124
+ address indexed coreContract
125
+ );
126
+
127
+ /**
128
+ * @notice Next token ID for project `projectId` updated
129
+ * @param projectId Project Id of next token updated
130
+ * @param coreContract Core contract address of next token updated
131
+ * @param tokenId Token Id of next token updated
132
+ */
133
+ event ProjectNextTokenUpdated(
134
+ uint256 indexed projectId,
135
+ address indexed coreContract,
136
+ uint256 tokenId
137
+ );
138
+
139
+ /**
140
+ * @notice Next token ID for project `projectId` ejected from the minter
141
+ * and is no longer populated
142
+ * @param projectId Project Id of next token ejected
143
+ * @param coreContract Core contract address of next token ejected
144
+ */
145
+ event ProjectNextTokenEjected(
146
+ uint256 indexed projectId,
147
+ address indexed coreContract
148
+ );
149
+
150
+ // position of SEA Lib storage, using a diamond storage pattern
151
+ // for this library
152
+ bytes32 constant SEA_LIB_STORAGE_POSITION = keccak256("sealib.storage");
17
153
 
18
154
  uint256 constant ONE_MILLION = 1_000_000;
19
155
 
@@ -67,93 +203,436 @@ library SEALib {
67
203
  bool settled;
68
204
  }
69
205
 
206
+ // Diamond storage pattern is used in this library
207
+ struct SEALibStorage {
208
+ mapping(address coreContract => mapping(uint256 projectId => SEAProjectConfig)) SEAProjectConfigs;
209
+ }
210
+
70
211
  /**
71
212
  * @notice Updates the SEAProjectConfig to clear the current settings for
72
213
  * future auctions on the project. Values are reset to their default
73
214
  * initial values.
74
215
  * Current auction details are not cleared or affected.
75
- * @param _SEAProjectConfig SEAProjectConfig to update
216
+ * @param projectId Project ID to reset
217
+ * @param coreContract Core contract address to reset
76
218
  */
77
219
  function resetFutureAuctionDetails(
78
- SEAProjectConfig storage _SEAProjectConfig
220
+ uint256 projectId,
221
+ address coreContract
79
222
  ) internal {
223
+ SEAProjectConfig storage SEAProjectConfig_ = getSEAProjectConfig({
224
+ projectId: projectId,
225
+ coreContract: coreContract
226
+ });
80
227
  // reset future auction details
81
- _SEAProjectConfig.auctionDurationSeconds = 0;
82
- _SEAProjectConfig.minBidIncrementPercentage = 0;
83
- _SEAProjectConfig.basePrice = 0;
84
- _SEAProjectConfig.timestampStart = 0;
228
+ SEAProjectConfig_.auctionDurationSeconds = 0;
229
+ SEAProjectConfig_.minBidIncrementPercentage = 0;
230
+ SEAProjectConfig_.basePrice = 0;
231
+ SEAProjectConfig_.timestampStart = 0;
232
+
233
+ emit ResetAuctionDetails({
234
+ projectId: projectId,
235
+ coreContract: coreContract
236
+ });
85
237
  }
86
238
 
87
239
  /**
88
- * Overwrite the active auction for a project with a new auction.
89
- * @dev This function is used to initialize a new auction. Care must be
90
- * taken to ensure that the existing auction is fully complete and settled.
91
- * @param _SEAProjectConfig SEAProjectConfig to update
92
- * @param _targetTokenId token ID to create the auction for
93
- * @param _bidAmount initial bid amount
94
- * @param _bidder initial bidder's payable address
95
- * @return endTime end time of the newly created auction
240
+ * @notice Configures future auctions for project `projectId` on core
241
+ * contract `coreContract`.
242
+ * Reverts if `timestampStart` is in the past and not zero.
243
+ * @param projectId Project ID to configure
244
+ * @param coreContract Core contract address to configure
245
+ * @param timestampStart Timestamp to start future auctions at. If zero,
246
+ * auctions can start immediately.
247
+ * @param auctionDurationSeconds Duration of each new auction, before any
248
+ * extensions due to late bids
249
+ * @param basePrice Minimum bid price for the token auctions
250
+ * @param minBidIncrementPercentage Minimum bid increment percentage. Each
251
+ * subsequent bid must be at least this percentage greater than the
252
+ * previous bid. The value is expressed as a whole percentage, e.g. 5% is
253
+ * 5, 10% is 10, etc.
96
254
  */
97
- function overwriteProjectActiveAuction(
98
- SEAProjectConfig storage _SEAProjectConfig,
99
- uint256 _targetTokenId,
100
- uint256 _bidAmount,
101
- address payable _bidder
102
- ) internal returns (uint64 endTime) {
103
- // calculate auction end time
104
- endTime = (block.timestamp + _SEAProjectConfig.auctionDurationSeconds)
105
- .toUint64();
106
- // set active auction on SEAProjectConfig
107
- _SEAProjectConfig.activeAuction = Auction({
108
- tokenId: _targetTokenId,
109
- currentBid: _bidAmount,
110
- currentBidder: _bidder,
111
- endTime: endTime,
112
- minBidIncrementPercentage: _SEAProjectConfig
113
- .minBidIncrementPercentage,
114
- settled: false
255
+ function configureFutureAuctions(
256
+ uint256 projectId,
257
+ address coreContract,
258
+ uint256 timestampStart,
259
+ uint256 auctionDurationSeconds,
260
+ uint256 basePrice,
261
+ uint8 minBidIncrementPercentage
262
+ ) internal {
263
+ SEAProjectConfig storage SEAProjectConfig_ = getSEAProjectConfig({
264
+ projectId: projectId,
265
+ coreContract: coreContract
266
+ });
267
+ require(
268
+ timestampStart == 0 || block.timestamp < timestampStart,
269
+ "Only future start times or 0"
270
+ );
271
+ // EFFECTS
272
+ SEAProjectConfig_.timestampStart = timestampStart.toUint64(); // @dev rely on this safe-cast check in event emit below
273
+ SEAProjectConfig_.auctionDurationSeconds = auctionDurationSeconds
274
+ .toUint32();
275
+ SEAProjectConfig_.basePrice = basePrice;
276
+ SEAProjectConfig_.minBidIncrementPercentage = minBidIncrementPercentage;
277
+
278
+ emit ConfiguredFutureAuctions({
279
+ projectId: projectId,
280
+ coreContract: coreContract,
281
+ timestampStart: uint64(timestampStart), // @dev safe-casting already previously checked
282
+ auctionDurationSeconds: auctionDurationSeconds.toUint32(),
283
+ basePrice: basePrice,
284
+ minBidIncrementPercentage: minBidIncrementPercentage
115
285
  });
116
- return endTime;
117
286
  }
118
287
 
119
288
  /**
120
289
  * @notice Updates the bid state of an Auction.
121
290
  * Note that the auction's end time is extended if the bid is placed within
122
- * the last `_timeBufferSeconds` of the auction.
291
+ * the last `timeBufferSeconds` of the auction.
123
292
  * NOTE: This function does not check if the bid is valid. It is assumed
124
293
  * that the bid has already been checked for validity by the caller.
125
- * @param _auction Auction to update
126
- * @param _timeBufferSeconds Time buffer to extend the auction to if the
127
- * bid is placed within the last `_timeBufferSeconds` of the auction.
128
- * @param _bidAmount bid amount
129
- * @param _bidder bidder's payable address
294
+ * @param auction Auction to update
295
+ * @param timeBufferSeconds Time buffer to extend the auction to if the
296
+ * bid is placed within the last `timeBufferSeconds` of the auction.
297
+ * @param bidAmount bid amount
298
+ * @param bidder bidder's payable address
130
299
  */
131
300
  function auctionUpdateBid(
132
- Auction storage _auction,
133
- uint256 _timeBufferSeconds,
134
- uint256 _bidAmount,
135
- address payable _bidder
301
+ Auction storage auction,
302
+ uint256 timeBufferSeconds,
303
+ uint256 bidAmount,
304
+ address payable bidder
136
305
  ) internal {
137
306
  // update auction state
138
- _auction.currentBid = _bidAmount;
139
- _auction.currentBidder = _bidder;
140
- uint256 minEndTime = block.timestamp + _timeBufferSeconds;
141
- if (_auction.endTime < minEndTime) {
142
- _auction.endTime = minEndTime.toUint64();
307
+ auction.currentBid = bidAmount;
308
+ auction.currentBidder = bidder;
309
+ uint256 minEndTime = block.timestamp + timeBufferSeconds;
310
+ if (auction.endTime < minEndTime) {
311
+ auction.endTime = minEndTime.toUint64();
143
312
  }
144
313
  }
145
314
 
315
+ /**
316
+ * @notice Ejects a project's "next token" from the minter and sends it to
317
+ * the input `to` address.
318
+ * This function is only intended for use in the edge case where the minter
319
+ * has a "next token" assigned to a project, but the project has been reset
320
+ * via `resetAuctionDetails`, and the artist does not want an auction to be
321
+ * started for the "next token". This function also protects against the
322
+ * unforseen case where the minter is in an unexpected state where it has a
323
+ * "next token" assigned to a project, but for some reason the project is
324
+ * unable to begin a new auction due to a bug.
325
+ * @param projectId The project ID being ejected
326
+ * @param coreContract The core contract address being ejected
327
+ * @param to The address to send the ejected token to
328
+ */
329
+ function ejectNextTokenTo(
330
+ uint256 projectId,
331
+ address coreContract,
332
+ address to
333
+ ) internal {
334
+ SEAProjectConfig storage SEAProjectConfig_ = getSEAProjectConfig({
335
+ projectId: projectId,
336
+ coreContract: coreContract
337
+ });
338
+ // CHECKS
339
+ // only if project is not configured (i.e. artist called
340
+ // `resetAuctionDetails`)
341
+ require(
342
+ !projectIsConfigured({
343
+ projectId: projectId,
344
+ coreContract: coreContract
345
+ }),
346
+ "Only unconfigured projects"
347
+ );
348
+ // only if project has a next token assigned
349
+ require(
350
+ SEAProjectConfig_.nextTokenNumberIsPopulated == true,
351
+ "No next token"
352
+ );
353
+ // EFFECTS
354
+ SEAProjectConfig_.nextTokenNumberIsPopulated = false;
355
+ // INTERACTIONS
356
+ // @dev overflow automatically handled by Sol ^0.8.0
357
+ uint256 nextTokenId = ABHelpers.tokenIdFromProjectIdAndTokenNumber({
358
+ projectId: projectId,
359
+ tokenNumber: SEAProjectConfig_.nextTokenNumber
360
+ });
361
+ IERC721(coreContract).transferFrom({
362
+ from: address(this),
363
+ to: to,
364
+ tokenId: nextTokenId
365
+ });
366
+ emit ProjectNextTokenEjected({
367
+ projectId: projectId,
368
+ coreContract: coreContract
369
+ });
370
+ }
371
+
372
+ /**
373
+ * @notice Settles a completed auction for `tokenId`, if it exists and is
374
+ * not yet settled.
375
+ * Returns early (does not modify state) if
376
+ * - there is no initialized auction for the project
377
+ * - there is an auction that has already been settled for the project
378
+ * - there is an auction for a different token ID on the project
379
+ * (likely due to a front-run)
380
+ * This function reverts if the auction for `tokenId` exists, but has not
381
+ * yet ended.
382
+ * @param tokenId Token ID to settle auction for.
383
+ * @param coreContract Core contract address for the given token.
384
+ */
385
+ function settleAuction(uint256 tokenId, address coreContract) internal {
386
+ uint256 projectId = ABHelpers.tokenIdToProjectId(tokenId);
387
+ SEAProjectConfig storage _SEAProjectConfig = getSEAProjectConfig({
388
+ projectId: projectId,
389
+ coreContract: coreContract
390
+ });
391
+ Auction storage auction = _SEAProjectConfig.activeAuction;
392
+ // load from storage to memory for gas efficiency
393
+ address currentBidder = auction.currentBidder;
394
+ uint256 currentBid = auction.currentBid;
395
+ // CHECKS
396
+ // @dev this check is not strictly necessary, but is included for
397
+ // clear error messaging
398
+ require(auctionIsInitialized(auction), "Auction not initialized");
399
+ if (auction.settled || (auction.tokenId != tokenId)) {
400
+ // auction already settled or is for a different token ID, so
401
+ // return early and do not modify state
402
+ return;
403
+ }
404
+ // @dev important that the following check is after the early return
405
+ // block above to maintain desired behavior
406
+ require(block.timestamp >= auction.endTime, "Auction not yet ended");
407
+ // EFFECTS
408
+ auction.settled = true;
409
+ // INTERACTIONS
410
+ // send token to the winning bidder
411
+ IERC721(coreContract).transferFrom({
412
+ from: address(this),
413
+ to: currentBidder,
414
+ tokenId: tokenId
415
+ });
416
+ // distribute revenues from auction
417
+ SplitFundsLib.splitRevenuesETHNoRefund({
418
+ projectId: projectId,
419
+ valueInWei: currentBid,
420
+ coreContract: coreContract
421
+ });
422
+
423
+ emit AuctionSettled({
424
+ tokenId: tokenId,
425
+ coreContract: coreContract,
426
+ winner: currentBidder,
427
+ price: currentBid
428
+ });
429
+ }
430
+
431
+ /**
432
+ * @notice Enters a bid for token `tokenId`.
433
+ * If an auction for token `tokenId` does not exist, an auction will be
434
+ * initialized as long as any existing auction for the project has been
435
+ * settled.
436
+ * In order to successfully place the bid, the token bid must be:
437
+ * - greater than or equal to a project's minimum bid price if a new
438
+ * auction is initialized
439
+ * - sufficiently greater than the current highest bid, according to the
440
+ * minter's bid increment percentage `minterMinBidIncrementPercentage`,
441
+ * if an auction for the token already exists
442
+ * If the bid is unsuccessful, the transaction will revert.
443
+ * If the bid is successful, but outbid by another bid before the auction
444
+ * ends, the funds will be noncustodially returned to the bidder's address,
445
+ * `msg.sender`. A fallback method of sending funds back to the bidder via
446
+ * SELFDESTRUCT (SENDALL) prevents denial of service attacks, even if the
447
+ * original bidder reverts or runs out of gas during receive or fallback.
448
+ * ------------------------------------------------------------------------
449
+ * WARNING: bidders must be prepared to handle the case where their bid is
450
+ * outbid and their funds are returned to the original `msg.sender` address
451
+ * via SELFDESTRUCT (SENDALL).
452
+ * ------------------------------------------------------------------------
453
+ * Note that the use of `tokenId` is to prevent the possibility of
454
+ * transactions that are stuck in the pending pool for long periods of time
455
+ * from unintentionally bidding on auctions for future tokens.
456
+ * If a new auction is initialized during this call, the project's next
457
+ * token will be attempted to be minted to this minter contract, preparing
458
+ * it for the next auction. If the project's next token cannot be minted
459
+ * due to e.g. reaching the maximum invocations on the core contract or
460
+ * minter, the project's next token will not be minted.
461
+ * @param tokenId Token ID being bid on.
462
+ * @param coreContract Core contract address for the given project.
463
+ * @param minterTimeBufferSeconds Time buffer to extend the auction to if
464
+ * the bid is placed within the last `minterTimeBufferSeconds` of the
465
+ * auction.
466
+ * @param minterRefundGasLimit Gas limit to use when refunding the previous
467
+ * highest bidder.
468
+ * @param minterFilter Minter filter to use when minting the next token for
469
+ * the project.
470
+ * @dev nonReentrant modifier is used to prevent reentrancy attacks, e.g.
471
+ * an an auto-bidder that would be able to atomically outbid a user's
472
+ * new bid via a reentrant call to createBid.
473
+ */
474
+ function createBid(
475
+ uint256 tokenId,
476
+ address coreContract,
477
+ uint256 minterTimeBufferSeconds,
478
+ uint256 minterRefundGasLimit,
479
+ IMinterFilterV1 minterFilter
480
+ ) internal {
481
+ uint256 projectId = ABHelpers.tokenIdToProjectId(tokenId);
482
+ SEAProjectConfig storage SEAProjectConfig_ = getSEAProjectConfig({
483
+ projectId: projectId,
484
+ coreContract: coreContract
485
+ });
486
+ Auction storage auction = SEAProjectConfig_.activeAuction;
487
+ // CHECKS
488
+ // if no auction exists, or current auction is already settled, attempt
489
+ // to initialize a new auction for the input token ID and immediately
490
+ // return
491
+ if ((!auctionIsInitialized(auction)) || auction.settled) {
492
+ _initializeAuctionWithBid({
493
+ projectId: projectId,
494
+ coreContract: coreContract,
495
+ targetTokenId: tokenId,
496
+ minterFilter: minterFilter
497
+ });
498
+ return;
499
+ }
500
+ // @dev this branch is guaranteed to have an initialized auction that
501
+ // not settled, so no need to check for initialized or not settled
502
+
503
+ // ensure bids for a specific token ID are only applied to the auction
504
+ // for that token ID.
505
+ require(auction.tokenId == tokenId, "Token ID does not match auction");
506
+
507
+ // ensure auction is not already ended
508
+ require(auction.endTime > block.timestamp, "Auction already ended");
509
+
510
+ // require bid to be sufficiently greater than current highest bid
511
+ // @dev no overflow enforced automatically by solidity ^0.8.0
512
+ require(msg.value >= getMinimumNextBid(auction), "Bid is too low");
513
+
514
+ // EFFECTS
515
+ // record previous highest bidder and bid for refunding
516
+ address payable previousBidder = auction.currentBidder;
517
+ uint256 previousBid = auction.currentBid;
518
+
519
+ // update auction bid state
520
+ auctionUpdateBid({
521
+ auction: auction,
522
+ timeBufferSeconds: minterTimeBufferSeconds,
523
+ bidAmount: msg.value,
524
+ bidder: payable(msg.sender)
525
+ });
526
+
527
+ // INTERACTIONS
528
+ // refund previous highest bidder
529
+ SplitFundsLib.forceSafeTransferETH({
530
+ to: previousBidder,
531
+ amount: previousBid,
532
+ minterRefundGasLimit: minterRefundGasLimit
533
+ });
534
+
535
+ emit AuctionBid({
536
+ tokenId: tokenId,
537
+ coreContract: coreContract,
538
+ bidder: msg.sender,
539
+ bidAmount: msg.value
540
+ });
541
+ }
542
+
543
+ /**
544
+ * @notice Internal function that attempts to mint a new token to the next
545
+ * token slot for the project `projectId`.
546
+ * This function returns early and does not modify state if
547
+ * - the project has reached its maximum invocations on either the core
548
+ * contract or minter
549
+ * - the project config's `nextTokenNumberIsPopulated` is already true
550
+ * @param projectId The ID of the project to mint a new token for.
551
+ * @param coreContract The core contract address
552
+ * @param minterFilter The minter filter contract address
553
+ * @dev this calls mint with `msg.sender` as the sender, allowing artists
554
+ * to mint tokens to the next token slot for their project while a project
555
+ * is still paused. This happens when an artist is configuring their
556
+ * project's auction parameters or minter max invocations.
557
+ */
558
+ function tryMintTokenToNextSlot(
559
+ uint256 projectId,
560
+ address coreContract,
561
+ IMinterFilterV1 minterFilter
562
+ ) internal {
563
+ SEAProjectConfig storage SEAProjectConfig_ = getSEAProjectConfig({
564
+ projectId: projectId,
565
+ coreContract: coreContract
566
+ });
567
+ if (SEAProjectConfig_.nextTokenNumberIsPopulated) {
568
+ return;
569
+ }
570
+ // INTERACTIONS
571
+ // attempt to mint new token to this minter contract, only if max
572
+ // invocations has not been reached
573
+ // we require up-to-date invocation data to properly handle last token
574
+ // and avoid revert if relying on core to limit invocations, therefore
575
+ // use MaxInvocationsLib.invocationsRemain, which calls core contract
576
+ // to get latest invocation data
577
+ if (
578
+ !MaxInvocationsLib.invocationsRemain({
579
+ projectId: projectId,
580
+ coreContract: coreContract
581
+ })
582
+ ) {
583
+ // we have reached the max invocations, so we do not mint a new
584
+ // token as the "next token", and leave the next token number as
585
+ // not populated
586
+ return;
587
+ }
588
+ // @dev this is an effect after a trusted contract interaction
589
+ SEAProjectConfig_.nextTokenNumberIsPopulated = true;
590
+ // mint a new token to this project's "next token" slot
591
+ // @dev this is an interaction with a trusted contract
592
+ uint256 nextTokenId = minterFilter.mint_joo({
593
+ to: address(this),
594
+ projectId: projectId,
595
+ coreContract: coreContract,
596
+ sender: msg.sender
597
+ });
598
+ // update state to reflect new token number
599
+ // @dev state changes after trusted contract interaction
600
+ // @dev unchecked is safe because mod 1e6 is guaranteed to be less than
601
+ // max uint24
602
+ unchecked {
603
+ SEAProjectConfig_.nextTokenNumber = uint24(
604
+ ABHelpers.tokenIdToTokenNumber(nextTokenId)
605
+ );
606
+ }
607
+ // update local maxHasBeenInvoked value if necessary
608
+ MaxInvocationsLib.validatePurchaseEffectsInvocations({
609
+ tokenId: nextTokenId,
610
+ coreContract: coreContract
611
+ });
612
+ emit ProjectNextTokenUpdated({
613
+ projectId: projectId,
614
+ coreContract: coreContract,
615
+ tokenId: nextTokenId
616
+ });
617
+ }
618
+
146
619
  /**
147
620
  * @notice Determines if a project is configured or not on this minter.
148
621
  * Uses project config's `auctionDurationSeconds` to determine if project
149
622
  * is configured, because `auctionDurationSeconds` is required to be
150
623
  * non-zero when configured.
151
- * @param _SEAProjectConfig The project config to check.
624
+ * @param projectId The project ID being queried
625
+ * @param coreContract The core contract address being queried
152
626
  */
153
627
  function projectIsConfigured(
154
- SEAProjectConfig storage _SEAProjectConfig
628
+ uint256 projectId,
629
+ address coreContract
155
630
  ) internal view returns (bool) {
156
- return _SEAProjectConfig.auctionDurationSeconds != 0;
631
+ SEAProjectConfig storage SEAProjectConfig_ = getSEAProjectConfig({
632
+ projectId: projectId,
633
+ coreContract: coreContract
634
+ });
635
+ return SEAProjectConfig_.auctionDurationSeconds != 0;
157
636
  }
158
637
 
159
638
  /**
@@ -161,46 +640,51 @@ library SEALib {
161
640
  * Uses auction's `currentBidder` address to determine if auction is
162
641
  * initialized, because `currentBidder` is always non-zero after an auction
163
642
  * has been initialized.
164
- * @param _auction The auction to check.
643
+ * @param auction The auction to check.
165
644
  */
166
645
  function auctionIsInitialized(
167
- Auction storage _auction
646
+ Auction storage auction
168
647
  ) internal view returns (bool isInitialized) {
169
648
  // auction is initialized if currentBidder is non-zero
170
- return _auction.currentBidder != address(0);
649
+ return auction.currentBidder != address(0);
171
650
  }
172
651
 
173
652
  /**
174
- * Returns bool representing if an auction is accepting bids above base
653
+ * @notice Function returns bool representing if an auction is accepting bids above base
175
654
  * price. It is accepting bids if it is initialized and has not reached its
176
655
  * end time.
177
- * @param _auction The auction to check.
656
+ * @param auction The auction to check.
178
657
  */
179
658
  function auctionIsAcceptingIncreasingBids(
180
- Auction storage _auction
659
+ Auction storage auction
181
660
  ) internal view returns (bool isAcceptingBids) {
182
661
  // auction is accepting bids if it is initialized and has not reached
183
662
  // its end time
184
- isAcceptingBids = (auctionIsInitialized(_auction) &&
185
- block.timestamp < _auction.endTime);
186
- return isAcceptingBids;
663
+ isAcceptingBids = (auctionIsInitialized(auction) &&
664
+ block.timestamp < auction.endTime);
187
665
  }
188
666
 
189
667
  /**
190
668
  * @notice SEAProjectConfig => active auction details.
191
669
  * @dev reverts if no auction exists for the project.
192
- * @param _SEAProjectConfig SEAProjectConfig to query
670
+ * @param projectId The project ID being queried
671
+ * @param coreContract The core contract address being queried
193
672
  */
194
673
  function projectActiveAuctionDetails(
195
- SEAProjectConfig storage _SEAProjectConfig
674
+ uint256 projectId,
675
+ address coreContract
196
676
  ) internal view returns (Auction memory auction) {
197
- Auction storage _auction = _SEAProjectConfig.activeAuction;
677
+ SEAProjectConfig storage SEAProjectConfig_ = getSEAProjectConfig({
678
+ projectId: projectId,
679
+ coreContract: coreContract
680
+ });
681
+ // load auction storage pointer to pass ref to utility function
682
+ Auction storage auction_ = SEAProjectConfig_.activeAuction;
198
683
  // do not return uninitialized auctions (i.e. auctions that do not
199
684
  // exist, where currentBidder is still the default value)
200
- require(auctionIsInitialized(_auction), "No auction exists on project");
201
- // load entire auction into memory
202
- auction = _SEAProjectConfig.activeAuction;
203
- return auction;
685
+ require(auctionIsInitialized(auction_), "No auction exists on project");
686
+ // load entire auction into memory to return
687
+ auction = SEAProjectConfig_.activeAuction;
204
688
  }
205
689
 
206
690
  /**
@@ -213,47 +697,55 @@ library SEALib {
213
697
  * target bid token ID to be passed in as an argument.
214
698
  * The function reverts if a project does not have an active auction and
215
699
  * the next expected token ID has not been populated.
216
- * @param _SEAProjectConfig SEAProjectConfig to query
217
- * @param _projectId The project ID being queried
700
+ * @param projectId The project ID being queried
701
+ * @param coreContract The core contract address being queried
218
702
  * @return The current token ID being auctioned, or the next token ID to be
219
703
  * auctioned if a new auction is ready to be created.
220
704
  */
221
705
  function getTokenToBid(
222
- SEAProjectConfig storage _SEAProjectConfig,
223
- uint256 _projectId
706
+ uint256 projectId,
707
+ address coreContract
224
708
  ) internal view returns (uint256) {
225
- Auction storage _auction = _SEAProjectConfig.activeAuction;
709
+ SEAProjectConfig storage SEAProjectConfig_ = getSEAProjectConfig({
710
+ projectId: projectId,
711
+ coreContract: coreContract
712
+ });
713
+ Auction storage auction = SEAProjectConfig_.activeAuction;
226
714
  // if project has an active token auction that is not settled, return
227
715
  // that token ID
228
716
  if (
229
- auctionIsInitialized(_auction) &&
230
- (_auction.endTime > block.timestamp)
717
+ auctionIsInitialized(auction) && (auction.endTime > block.timestamp)
231
718
  ) {
232
- return _auction.tokenId;
719
+ return auction.tokenId;
233
720
  }
234
721
  // otherwise, return the next expected token ID to be auctioned.
235
- return getNextTokenId(_SEAProjectConfig, _projectId);
722
+ return getNextTokenId(projectId, coreContract);
236
723
  }
237
724
 
238
725
  /**
239
726
  * @notice View function that returns the next token ID to be auctioned
240
- * by this minter for project `_projectId`.
727
+ * by this minter for project `projectId`.
241
728
  * Reverts if the next token ID has not been populated for the project.
242
- * @param _SEAProjectConfig SEAProjectConfig to query
243
- * @param _projectId The project ID being queried
729
+ * @param projectId The project ID being queried
730
+ * @param coreContract The core contract address being queried
244
731
  */
245
732
  function getNextTokenId(
246
- SEAProjectConfig storage _SEAProjectConfig,
247
- uint256 _projectId
733
+ uint256 projectId,
734
+ address coreContract
248
735
  ) internal view returns (uint256 nextTokenId) {
249
- if (!_SEAProjectConfig.nextTokenNumberIsPopulated) {
250
- revert("Next token not populated");
251
- }
736
+ SEAProjectConfig storage SEAProjectConfig_ = getSEAProjectConfig({
737
+ projectId: projectId,
738
+ coreContract: coreContract
739
+ });
740
+ require(
741
+ SEAProjectConfig_.nextTokenNumberIsPopulated,
742
+ "Next token not populated"
743
+ );
744
+
252
745
  // @dev overflow automatically checked in Solidity ^0.8.0
253
746
  nextTokenId =
254
- (_projectId * ONE_MILLION) +
255
- _SEAProjectConfig.nextTokenNumber;
256
- return nextTokenId;
747
+ (projectId * ONE_MILLION) +
748
+ SEAProjectConfig_.nextTokenNumber;
257
749
  }
258
750
 
259
751
  /**
@@ -269,20 +761,29 @@ library SEALib {
269
761
  * the project is configured, `isConfigured` will be true, and
270
762
  * `tokenPriceInWei` will be the minimum initial bid price for the next
271
763
  * token auction.
272
- * @param _SEAProjectConfig SEAProjectConfig to be evaluated.
764
+ * @param projectId Project ID to get price info for
765
+ * @param coreContract Core contract address to get price info for
273
766
  * @return isConfigured true only if project auctions are configured.
274
767
  * @return tokenPriceInWei price in wei to become the leading bidder on a
275
768
  * token auction.
276
769
  */
277
- function getPriceInfoFromSEAProjectConfig(
278
- SEAProjectConfig storage _SEAProjectConfig
770
+ function getPriceInfo(
771
+ uint256 projectId,
772
+ address coreContract
279
773
  ) internal view returns (bool isConfigured, uint256 tokenPriceInWei) {
280
- Auction storage _auction = _SEAProjectConfig.activeAuction;
774
+ SEAProjectConfig storage SEAProjectConfig_ = getSEAProjectConfig({
775
+ projectId: projectId,
776
+ coreContract: coreContract
777
+ });
778
+ Auction storage auction = SEAProjectConfig_.activeAuction;
281
779
  // base price of zero not allowed when configuring auctions, so use it
282
780
  // as indicator of whether auctions are configured for the project
283
- bool projectIsConfigured_ = projectIsConfigured(_SEAProjectConfig);
781
+ bool projectIsConfigured_ = projectIsConfigured({
782
+ projectId: projectId,
783
+ coreContract: coreContract
784
+ });
284
785
  bool auctionIsAcceptingIncreasingBids_ = auctionIsAcceptingIncreasingBids(
285
- _auction
786
+ auction
286
787
  );
287
788
  isConfigured =
288
789
  projectIsConfigured_ ||
@@ -291,12 +792,12 @@ library SEALib {
291
792
  if (isConfigured) {
292
793
  if (auctionIsAcceptingIncreasingBids_) {
293
794
  // return minimum next bid, given current bid
294
- tokenPriceInWei = getMinimumNextBid(_auction);
795
+ tokenPriceInWei = getMinimumNextBid(auction);
295
796
  } else {
296
797
  // return base (starting) price if if current auction is not
297
798
  // accepting bids (i.e. the minimum initial bid price for the
298
799
  // next token auction)
299
- tokenPriceInWei = _SEAProjectConfig.basePrice;
800
+ tokenPriceInWei = SEAProjectConfig_.basePrice;
300
801
  }
301
802
  }
302
803
  // else leave tokenPriceInWei as default value of zero
@@ -304,17 +805,191 @@ library SEALib {
304
805
  }
305
806
 
306
807
  /**
307
- * Returns the minimum next bid amount in a configured auction, given the
808
+ * @notice Function returns the minimum next bid amount in a configured auction, given the
308
809
  * current bid amount and the project's configured minimum bid increment
309
810
  * percentage.
310
- * @param _auction Auction to query
811
+ * @param auction Auction to query
311
812
  */
312
813
  function getMinimumNextBid(
313
- Auction storage _auction
814
+ Auction storage auction
314
815
  ) internal view returns (uint256 minimumNextBid) {
315
- // @dev overflow automatically checked in Solidity ^0.8.0
316
- return
317
- (_auction.currentBid * (100 + _auction.minBidIncrementPercentage)) /
318
- 100;
816
+ // @dev unchecked due to 0 <= minBidIncrementPercentage <= 255, and
817
+ // currentBid is capped at supply of ETH, so no risk of overflow
818
+ unchecked {
819
+ return
820
+ (auction.currentBid *
821
+ (100 + auction.minBidIncrementPercentage)) / 100;
822
+ }
823
+ }
824
+
825
+ /**
826
+ * @dev Private function to initialize an auction for the next token ID
827
+ * on project `projectId` with a bid of `msg.value` from `msg.sender`.
828
+ * This function reverts in any of the following cases:
829
+ * - project is not configured on this minter
830
+ * - project is configured but has not yet reached its start time
831
+ * - project has a current active auction that is not settled
832
+ * - insufficient bid amount (msg.value < basePrice)
833
+ * - no next token has been minted for the project (artist may need to
834
+ * call `tryPopulateNextToken`)
835
+ * - `targetTokenId` does not match the next token ID for the project
836
+ * After initializing a new auction, this function attempts to mint a new
837
+ * token and assign it to the project's next token slot, in preparation for
838
+ * a future token auction. However, if the project has reached its maximum
839
+ * invocations on either the core contract or minter, the next token slot
840
+ * for the project will remain empty.
841
+ * @dev This should be executed in a nonReentrant context to provide redundant
842
+ * protection against reentrancy.
843
+ * @param projectId The project ID to initialize an auction for
844
+ * @param coreContract The core contract address to initialize an auction for
845
+ * @param targetTokenId The token ID to initialize an auction for
846
+ * @param minterFilter The minter filter contract address
847
+ */
848
+ function _initializeAuctionWithBid(
849
+ uint256 projectId,
850
+ address coreContract,
851
+ uint256 targetTokenId,
852
+ IMinterFilterV1 minterFilter
853
+ ) private {
854
+ SEAProjectConfig storage SEAProjectConfig_ = getSEAProjectConfig({
855
+ projectId: projectId,
856
+ coreContract: coreContract
857
+ });
858
+ Auction storage auction = SEAProjectConfig_.activeAuction;
859
+ // CHECKS
860
+ // ensure project auctions are configured
861
+ // @dev base price of zero indicates auctions are not configured
862
+ // because only base price of gt zero is allowed when configuring
863
+ require(
864
+ projectIsConfigured({
865
+ projectId: projectId,
866
+ coreContract: coreContract
867
+ }),
868
+ "Project not configured"
869
+ );
870
+ // only initialize new auctions if they meet the start time
871
+ // requirement
872
+ require(
873
+ block.timestamp >= SEAProjectConfig_.timestampStart,
874
+ "Only gte project start time"
875
+ );
876
+ // the following require statement is redundant based on how this
877
+ // internal function is called, but it is included for protection
878
+ // against future changes that could easily introduce a bug if this
879
+ // check is not present
880
+ // @dev no cover else branch of next line because unreachable
881
+ require(
882
+ (!auctionIsInitialized(auction)) || auction.settled,
883
+ "Existing auction not settled"
884
+ );
885
+ // require valid bid value
886
+ require(
887
+ msg.value >= SEAProjectConfig_.basePrice,
888
+ "Insufficient initial bid"
889
+ );
890
+ // require next token number is populated
891
+ // @dev this should only be encountered if the project has reached
892
+ // its maximum invocations on either the core contract or minter
893
+ require(
894
+ SEAProjectConfig_.nextTokenNumberIsPopulated,
895
+ "No next token, check max invocations"
896
+ );
897
+ // require next token number is the target token ID
898
+ require(
899
+ SEAProjectConfig_.nextTokenNumber ==
900
+ ABHelpers.tokenIdToTokenNumber(targetTokenId),
901
+ "Incorrect target token ID"
902
+ );
903
+
904
+ // EFFECTS
905
+ // create new auction, overwriting previous auction if it exists
906
+ uint64 endTime = overwriteProjectActiveAuction({
907
+ SEAProjectConfig_: SEAProjectConfig_,
908
+ targetTokenId: targetTokenId,
909
+ bidAmount: msg.value,
910
+ bidder: payable(msg.sender)
911
+ });
912
+ // mark next token number as not populated
913
+ // @dev intentionally not setting nextTokenNumber to zero to avoid
914
+ // unnecessary gas costs
915
+ SEAProjectConfig_.nextTokenNumberIsPopulated = false;
916
+
917
+ // @dev we intentionally emit event here due to potential of early
918
+ // return in INTERACTIONS section
919
+ emit AuctionInitialized({
920
+ tokenId: targetTokenId,
921
+ coreContract: coreContract,
922
+ bidder: msg.sender,
923
+ bidAmount: msg.value,
924
+ endTime: endTime,
925
+ minBidIncrementPercentage: SEAProjectConfig_
926
+ .activeAuction
927
+ .minBidIncrementPercentage
928
+ });
929
+
930
+ // INTERACTIONS
931
+ // attempt to mint new token to this minter contract, only if max
932
+ // invocations has not been reached
933
+ tryMintTokenToNextSlot({
934
+ projectId: projectId,
935
+ coreContract: coreContract,
936
+ minterFilter: minterFilter
937
+ });
938
+ }
939
+
940
+ /**
941
+ * @notice Function to overwrite the active auction for a project with a new auction.
942
+ * @dev This function is used to initialize a new auction. Care must be
943
+ * taken to ensure that the existing auction is fully complete and settled.
944
+ * @param SEAProjectConfig_ SEAProjectConfig to update
945
+ * @param targetTokenId token ID to create the auction for
946
+ * @param bidAmount initial bid amount
947
+ * @param bidder initial bidder's payable address
948
+ * @return endTime end time of the newly created auction
949
+ */
950
+ function overwriteProjectActiveAuction(
951
+ SEAProjectConfig storage SEAProjectConfig_,
952
+ uint256 targetTokenId,
953
+ uint256 bidAmount,
954
+ address payable bidder
955
+ ) private returns (uint64 endTime) {
956
+ // calculate auction end time
957
+ endTime = (block.timestamp + SEAProjectConfig_.auctionDurationSeconds)
958
+ .toUint64();
959
+ // set active auction on SEAProjectConfig
960
+ SEAProjectConfig_.activeAuction = Auction({
961
+ tokenId: targetTokenId,
962
+ currentBid: bidAmount,
963
+ currentBidder: bidder,
964
+ endTime: endTime,
965
+ minBidIncrementPercentage: SEAProjectConfig_
966
+ .minBidIncrementPercentage,
967
+ settled: false
968
+ });
969
+ }
970
+
971
+ /**
972
+ * Loads the SEAProjectConfig for a given project and core
973
+ * contract.
974
+ * @param projectId Project Id to get config for
975
+ * @param coreContract Core contract address to get config for
976
+ */
977
+ function getSEAProjectConfig(
978
+ uint256 projectId,
979
+ address coreContract
980
+ ) internal view returns (SEAProjectConfig storage) {
981
+ return s().SEAProjectConfigs[coreContract][projectId];
982
+ }
983
+
984
+ /**
985
+ * @notice Return the storage struct for reading and writing. This library
986
+ * uses a diamond storage pattern when managing storage.
987
+ * @return storageStruct The SEALibStorage struct.
988
+ */
989
+ function s() internal pure returns (SEALibStorage storage storageStruct) {
990
+ bytes32 position = SEA_LIB_STORAGE_POSITION;
991
+ assembly ("memory-safe") {
992
+ storageStruct.slot := position
993
+ }
319
994
  }
320
995
  }