@zoralabs/comments-contracts 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/.env.example +11 -0
  2. package/.turbo/turbo-build.log +60 -0
  3. package/LICENSE +21 -0
  4. package/README.md +70 -0
  5. package/_imagine/Enjoy.sol +41 -0
  6. package/abis/AccessControlUpgradeable.json +250 -0
  7. package/abis/Address.json +29 -0
  8. package/abis/Comments.json +62 -0
  9. package/abis/CommentsDeployerBase.json +15 -0
  10. package/abis/CommentsImpl.json +1750 -0
  11. package/abis/CommentsPermitTest.json +847 -0
  12. package/abis/CommentsTest.json +986 -0
  13. package/abis/CommentsTestBase.json +577 -0
  14. package/abis/Comments_mintAndCommentTest.json +690 -0
  15. package/abis/ContextUpgradeable.json +25 -0
  16. package/abis/ContractVersionBase.json +15 -0
  17. package/abis/Create2.json +28 -0
  18. package/abis/DeployImpl.json +22 -0
  19. package/abis/DeployNonDeterministic.json +22 -0
  20. package/abis/DeployScript.json +22 -0
  21. package/abis/DeterministicDeployerAndCaller.json +315 -0
  22. package/abis/DeterministicUUPSProxyDeployer.json +167 -0
  23. package/abis/ECDSA.json +29 -0
  24. package/abis/EIP712.json +67 -0
  25. package/abis/EIP712UpgradeableWithChainId.json +25 -0
  26. package/abis/ERC1155.json +416 -0
  27. package/abis/ERC1155Holder.json +99 -0
  28. package/abis/ERC165.json +21 -0
  29. package/abis/ERC165Upgradeable.json +44 -0
  30. package/abis/ERC1967Proxy.json +67 -0
  31. package/abis/ERC1967Utils.json +85 -0
  32. package/abis/GenerateDeterministicParams.json +22 -0
  33. package/abis/IAccessControl.json +195 -0
  34. package/abis/IBeacon.json +15 -0
  35. package/abis/IComments.json +654 -0
  36. package/abis/IContractMetadata.json +28 -0
  37. package/abis/IERC1155.json +295 -0
  38. package/abis/IERC1155Errors.json +104 -0
  39. package/abis/IERC1155MetadataURI.json +314 -0
  40. package/abis/IERC1155Receiver.json +99 -0
  41. package/abis/IERC1271.json +26 -0
  42. package/abis/IERC165.json +21 -0
  43. package/abis/IERC1822Proxiable.json +15 -0
  44. package/abis/IERC20.json +224 -0
  45. package/abis/IERC20Errors.json +88 -0
  46. package/abis/IERC5267.json +51 -0
  47. package/abis/IERC721.json +287 -0
  48. package/abis/IERC721Enumerable.json +343 -0
  49. package/abis/IERC721Errors.json +105 -0
  50. package/abis/IERC721Metadata.json +332 -0
  51. package/abis/IERC721TokenReceiver.json +36 -0
  52. package/abis/IHasContractName.json +15 -0
  53. package/abis/IImmutableCreate2Factory.json +93 -0
  54. package/abis/IMulticall3.json +440 -0
  55. package/abis/IProtocolRewards.json +342 -0
  56. package/abis/ISafe.json +15 -0
  57. package/abis/ISymbol.json +15 -0
  58. package/abis/IVersionedContract.json +15 -0
  59. package/abis/IZoraCreator1155.json +343 -0
  60. package/abis/ImmutableCreate2FactoryUtils.json +15 -0
  61. package/abis/Initializable.json +25 -0
  62. package/abis/LibString.json +7 -0
  63. package/abis/Math.json +7 -0
  64. package/abis/Mock1155.json +547 -0
  65. package/abis/MockERC20.json +322 -0
  66. package/abis/MockERC721.json +350 -0
  67. package/abis/MockMinter.json +64 -0
  68. package/abis/OwnableUpgradeable.json +99 -0
  69. package/abis/ProtocolRewards.json +494 -0
  70. package/abis/Proxy.json +6 -0
  71. package/abis/ProxyDeployerScript.json +15 -0
  72. package/abis/ProxyShim.json +112 -0
  73. package/abis/Script.json +15 -0
  74. package/abis/ShortStrings.json +18 -0
  75. package/abis/StdAssertions.json +379 -0
  76. package/abis/StdInvariant.json +180 -0
  77. package/abis/Strings.json +18 -0
  78. package/abis/Test.json +570 -0
  79. package/abis/UUPSUpgradeable.json +130 -0
  80. package/abis/UnorderedNoncesUpgradeable.json +42 -0
  81. package/abis/Vm.json +8627 -0
  82. package/abis/VmSafe.json +7297 -0
  83. package/abis/stdError.json +119 -0
  84. package/abis/stdStorageSafe.json +52 -0
  85. package/addresses/999999999.json +4 -0
  86. package/deterministicConfig/comments.json +8 -0
  87. package/dist/index.cjs +935 -0
  88. package/dist/index.cjs.map +1 -0
  89. package/dist/index.d.ts +2 -0
  90. package/dist/index.d.ts.map +1 -0
  91. package/dist/index.js +908 -0
  92. package/dist/index.js.map +1 -0
  93. package/dist/types.d.ts +4 -0
  94. package/dist/types.d.ts.map +1 -0
  95. package/dist/wagmiGenerated.d.ts +1354 -0
  96. package/dist/wagmiGenerated.d.ts.map +1 -0
  97. package/foundry.toml +24 -0
  98. package/package/index.ts +4 -0
  99. package/package/types.ts +5 -0
  100. package/package/wagmiGenerated.ts +907 -0
  101. package/package.json +62 -0
  102. package/remappings.txt +8 -0
  103. package/script/CommentsDeployerBase.sol +60 -0
  104. package/script/Deploy.s.sol +66 -0
  105. package/script/DeployImpl.s.sol +26 -0
  106. package/script/DeployNonDeterministic.s.sol +43 -0
  107. package/script/GenerateDeterministicParams.s.sol +55 -0
  108. package/script/bundle-abis.ts +109 -0
  109. package/script/storage-check.sh +57 -0
  110. package/script/update-contract-version.ts +63 -0
  111. package/scripts/abis.ts +3 -0
  112. package/scripts/backfillComments.ts +176 -0
  113. package/scripts/generateCommentsTestData.ts +247 -0
  114. package/scripts/getCommentsAddresses.ts +10 -0
  115. package/scripts/queries.ts +73 -0
  116. package/scripts/queryAndSaveComments.ts +48 -0
  117. package/scripts/queryQuantityOfComments.ts +53 -0
  118. package/scripts/signDeployAndCall.ts +51 -0
  119. package/scripts/turnkey.ts +36 -0
  120. package/scripts/utils.ts +127 -0
  121. package/scripts/writeComments.ts +198 -0
  122. package/slither.config.json +7 -0
  123. package/src/CommentsImpl.sol +552 -0
  124. package/src/deployments/CommentsDeployment.sol +14 -0
  125. package/src/interfaces/IComments.sol +156 -0
  126. package/src/interfaces/IZoraCreator1155.sol +12 -0
  127. package/src/proxy/Comments.sol +43 -0
  128. package/src/utils/EIP712UpgradeableWithChainId.sol +36 -0
  129. package/src/version/ContractVersionBase.sol +14 -0
  130. package/test/Comments.t.sol +482 -0
  131. package/test/CommentsTestBase.sol +86 -0
  132. package/test/Comments_mintAndComment.t.sol +101 -0
  133. package/test/Comments_permit.t.sol +397 -0
  134. package/test/mocks/Mock1155.sol +50 -0
  135. package/test/mocks/MockMinter.sol +29 -0
  136. package/test/mocks/ProtocolRewards.sol +1497 -0
  137. package/tsconfig.build.json +10 -0
  138. package/tsconfig.json +9 -0
  139. package/tsup.config.ts +11 -0
  140. package/wagmi.config.ts +14 -0
@@ -0,0 +1,482 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.20;
3
+
4
+ import "forge-std/Test.sol";
5
+
6
+ import {CommentsImpl} from "../src/CommentsImpl.sol";
7
+ import {Comments} from "../src/proxy/Comments.sol";
8
+ import {IComments} from "../src/interfaces/IComments.sol";
9
+ import {Mock1155} from "./mocks/Mock1155.sol";
10
+ import {IProtocolRewards} from "@zoralabs/protocol-rewards/src/interfaces/IProtocolRewards.sol";
11
+ import {ProtocolRewards} from "./mocks/ProtocolRewards.sol";
12
+ import {UnorderedNoncesUpgradeable} from "@zoralabs/shared-contracts/utils/UnorderedNoncesUpgradeable.sol";
13
+ import {CommentsTestBase} from "./CommentsTestBase.sol";
14
+
15
+ contract CommentsTest is CommentsTestBase {
16
+ uint256 public constant ZORA_REWARD_PCT = 10;
17
+ uint256 public constant REFERRER_REWARD_PCT = 20;
18
+ uint256 internal constant BPS_TO_PERCENT_2_DECIMAL_PERCISION = 100;
19
+
20
+ function _setupCommenterWithTokenAndSparks(address commenter, uint64 sparksQuantity) internal {
21
+ vm.startPrank(commenter);
22
+ mock1155.mint(commenter, tokenId1, 1, "");
23
+ vm.stopPrank();
24
+ vm.deal(commenter, sparksQuantity * SPARKS_VALUE);
25
+ }
26
+
27
+ function _createCommentIdentifier(address commenter, bytes32 nonce) internal view returns (IComments.CommentIdentifier memory) {
28
+ return IComments.CommentIdentifier({commenter: commenter, contractAddress: address(mock1155), tokenId: tokenId1, nonce: nonce});
29
+ }
30
+
31
+ function testCommentContractName() public view {
32
+ assertEq(comments.contractName(), "Zora Comments");
33
+ }
34
+
35
+ function testCommentWhenCollectorHasTokenShouldEmitCommented() public {
36
+ vm.startPrank(collectorWithToken);
37
+ mock1155.mint(collectorWithToken, tokenId1, 1, "");
38
+ vm.stopPrank();
39
+
40
+ vm.deal(collectorWithToken, SPARKS_VALUE);
41
+
42
+ address contractAddress = address(mock1155);
43
+ uint256 tokenId = tokenId1;
44
+ address commenter = collectorWithToken;
45
+
46
+ IComments.CommentIdentifier memory expectedCommentIdentifier = _expectedCommentIdentifier(contractAddress, tokenId, commenter);
47
+
48
+ // blank replyTo
49
+ IComments.CommentIdentifier memory replyTo;
50
+
51
+ vm.expectEmit(true, true, true, true);
52
+ emit IComments.Commented(
53
+ comments.hashCommentIdentifier(expectedCommentIdentifier),
54
+ expectedCommentIdentifier,
55
+ 0,
56
+ replyTo,
57
+ 1,
58
+ "test comment",
59
+ block.timestamp,
60
+ address(0)
61
+ );
62
+ vm.prank(collectorWithToken);
63
+ comments.comment{value: SPARKS_VALUE}(collectorWithToken, contractAddress, tokenId, "test comment", replyTo, address(0));
64
+
65
+ uint256 zoraReward = (SPARKS_VALUE * (ZORA_REWARD_PCT + REFERRER_REWARD_PCT)) / BPS_TO_PERCENT_2_DECIMAL_PERCISION;
66
+ vm.assertEq(protocolRewards.balanceOf(collectorWithToken), 0);
67
+ vm.assertEq(protocolRewards.balanceOf(zoraRecipient), zoraReward);
68
+ vm.assertEq(protocolRewards.balanceOf(tokenAdmin), SPARKS_VALUE - zoraReward);
69
+ }
70
+
71
+ function testCommentBackfillBatchAddCommentShouldEmitCommented() public {
72
+ IComments.CommentIdentifier[] memory commentIdentifiers = new IComments.CommentIdentifier[](2);
73
+
74
+ commentIdentifiers[0] = IComments.CommentIdentifier({
75
+ commenter: makeAddr("commenter1"),
76
+ contractAddress: address(mock1155),
77
+ tokenId: tokenId1,
78
+ nonce: 0
79
+ });
80
+
81
+ commentIdentifiers[1] = IComments.CommentIdentifier({
82
+ commenter: makeAddr("commenter2"),
83
+ contractAddress: address(mock1155),
84
+ tokenId: tokenId2,
85
+ nonce: bytes32("1")
86
+ });
87
+
88
+ string[] memory texts = new string[](2);
89
+ texts[0] = "test comment 1";
90
+ texts[1] = "test comment 2";
91
+
92
+ uint256[] memory timestamps = new uint256[](2);
93
+
94
+ timestamps[0] = block.timestamp;
95
+ timestamps[1] = block.timestamp + 100;
96
+
97
+ bytes32[] memory originalTransactionHashes = new bytes32[](2);
98
+ originalTransactionHashes[0] = bytes32("1");
99
+ originalTransactionHashes[1] = bytes32("2");
100
+
101
+ vm.expectEmit(true, true, true, true);
102
+ // verify first comment is emitted
103
+ emit IComments.BackfilledComment({
104
+ commentId: comments.hashCommentIdentifier(commentIdentifiers[0]),
105
+ comment: commentIdentifiers[0],
106
+ text: texts[0],
107
+ timestamp: timestamps[0],
108
+ originalTransactionId: originalTransactionHashes[0]
109
+ });
110
+ vm.expectEmit(true, true, true, true);
111
+ // verify second comment is emitted
112
+ emit IComments.BackfilledComment({
113
+ commentId: comments.hashCommentIdentifier(commentIdentifiers[1]),
114
+ comment: commentIdentifiers[1],
115
+ text: texts[1],
116
+ timestamp: timestamps[1],
117
+ originalTransactionId: originalTransactionHashes[1]
118
+ });
119
+
120
+ vm.prank(commentsBackfiller);
121
+ comments.backfillBatchAddComment(commentIdentifiers, texts, timestamps, originalTransactionHashes);
122
+ }
123
+
124
+ function testCommentBackfillBatchAddCommentShouldRevertIfDuplicate() public {
125
+ IComments.CommentIdentifier[] memory commentIdentifiers = new IComments.CommentIdentifier[](2);
126
+
127
+ commentIdentifiers[0] = IComments.CommentIdentifier({
128
+ commenter: makeAddr("commenter1"),
129
+ contractAddress: address(mock1155),
130
+ tokenId: tokenId1,
131
+ nonce: 0
132
+ });
133
+
134
+ commentIdentifiers[1] = IComments.CommentIdentifier({
135
+ commenter: makeAddr("commenter2"),
136
+ contractAddress: address(mock1155),
137
+ tokenId: tokenId2,
138
+ nonce: keccak256("1")
139
+ });
140
+
141
+ string[] memory texts = new string[](2);
142
+ texts[0] = "test comment 1";
143
+ texts[1] = "test comment 2";
144
+
145
+ uint256[] memory timestamps = new uint256[](2);
146
+
147
+ timestamps[0] = block.timestamp;
148
+ timestamps[1] = block.timestamp + 100;
149
+
150
+ bytes32[] memory originalTransactionHashes = new bytes32[](2);
151
+ originalTransactionHashes[0] = bytes32("1");
152
+ originalTransactionHashes[1] = bytes32("2");
153
+
154
+ vm.prank(commentsBackfiller);
155
+ comments.backfillBatchAddComment(commentIdentifiers, texts, timestamps, originalTransactionHashes);
156
+
157
+ // ensure that when backfilling a duplicate, it reverts
158
+ vm.expectRevert(abi.encodeWithSelector(IComments.DuplicateComment.selector, comments.hashCommentIdentifier(commentIdentifiers[0])));
159
+ vm.prank(commentsBackfiller);
160
+ comments.backfillBatchAddComment(commentIdentifiers, texts, timestamps, originalTransactionHashes);
161
+ }
162
+
163
+ function testCommentBackfillBatchAddCommentShouldRevertIfArrayLengthMismatch() public {
164
+ IComments.CommentIdentifier[] memory commentIdentifiers = new IComments.CommentIdentifier[](2);
165
+ commentIdentifiers[1] = commentIdentifiers[0];
166
+
167
+ string[] memory texts = new string[](2);
168
+
169
+ uint256[] memory timestamps = new uint256[](1); // Mismatched length
170
+
171
+ bytes32[] memory originalTransactionHashes = new bytes32[](2);
172
+
173
+ vm.expectRevert(IComments.ArrayLengthMismatch.selector);
174
+ vm.prank(commentsBackfiller);
175
+ comments.backfillBatchAddComment(commentIdentifiers, texts, timestamps, originalTransactionHashes);
176
+ }
177
+
178
+ function testCommentSparkCommentWithZeroSparks() public {
179
+ IComments.CommentIdentifier memory commentIdentifier = IComments.CommentIdentifier({
180
+ commenter: collectorWithToken,
181
+ contractAddress: address(mock1155),
182
+ tokenId: tokenId1,
183
+ nonce: bytes32(0)
184
+ });
185
+ vm.expectRevert(abi.encodeWithSignature("MustSendAtLeastOneSpark()"));
186
+ comments.sparkComment(commentIdentifier, 0, address(0));
187
+ }
188
+
189
+ function testCommentSparkCommentWithInvalidAmount() public {
190
+ IComments.CommentIdentifier memory commentIdentifier = IComments.CommentIdentifier({
191
+ commenter: collectorWithToken,
192
+ contractAddress: address(mock1155),
193
+ tokenId: tokenId1,
194
+ nonce: bytes32(0)
195
+ });
196
+ vm.expectRevert(abi.encodeWithSelector(IComments.IncorrectETHAmountForSparks.selector, 0, SPARKS_VALUE));
197
+ comments.sparkComment{value: 0}(commentIdentifier, 1, address(0));
198
+ }
199
+
200
+ function testCommentSparkCommentOwnComment() public {
201
+ // comment
202
+ IComments.CommentIdentifier memory replyTo;
203
+ IComments.CommentIdentifier memory commentIdentifier = _mockComment(collectorWithToken, replyTo);
204
+
205
+ // spark own comment
206
+ vm.deal(collectorWithToken, SPARKS_VALUE);
207
+
208
+ vm.expectRevert(abi.encodeWithSignature("CannotSparkOwnComment()"));
209
+ vm.prank(collectorWithToken);
210
+ comments.sparkComment{value: SPARKS_VALUE}(commentIdentifier, 1, address(0));
211
+ }
212
+
213
+ function testCommentSparkCommentDoesNotExist() public {
214
+ IComments.CommentIdentifier memory commentIdentifier = IComments.CommentIdentifier({
215
+ commenter: collectorWithToken,
216
+ contractAddress: address(mock1155),
217
+ tokenId: tokenId1,
218
+ nonce: keccak256("123456")
219
+ });
220
+ vm.expectRevert(abi.encodeWithSignature("CommentDoesntExist()"));
221
+ comments.sparkComment{value: SPARKS_VALUE}(commentIdentifier, 1, address(0));
222
+ }
223
+
224
+ function testCommentSparkCommentValid(uint64 sparksQuantity) public {
225
+ vm.assume(sparksQuantity > 0 && sparksQuantity < 1_000_000_000_000_000);
226
+
227
+ // comment
228
+ IComments.CommentIdentifier memory replyTo;
229
+ IComments.CommentIdentifier memory commentIdentifier = _mockComment(collectorWithToken, replyTo);
230
+
231
+ // mint
232
+ address commentor2 = makeAddr("commentor2");
233
+
234
+ uint256 zoraRecipientBalanceBeforeSpark = protocolRewards.balanceOf(zoraRecipient);
235
+
236
+ // spark comment
237
+ vm.deal(commentor2, sparksQuantity * SPARKS_VALUE);
238
+
239
+ vm.expectEmit(true, true, true, true);
240
+ emit IComments.SparkedComment(comments.hashCommentIdentifier(commentIdentifier), commentIdentifier, sparksQuantity, commentor2, block.timestamp);
241
+ vm.prank(commentor2);
242
+ comments.sparkComment{value: sparksQuantity * SPARKS_VALUE}(commentIdentifier, sparksQuantity, address(0));
243
+
244
+ uint256 zoraReward = (sparksQuantity * SPARKS_VALUE * (ZORA_REWARD_PCT + REFERRER_REWARD_PCT)) / BPS_TO_PERCENT_2_DECIMAL_PERCISION;
245
+ vm.assertEq(protocolRewards.balanceOf(zoraRecipient) - zoraRecipientBalanceBeforeSpark, zoraReward);
246
+ vm.assertEq(protocolRewards.balanceOf(collectorWithToken), (sparksQuantity * SPARKS_VALUE) - zoraReward);
247
+ }
248
+
249
+ function testCommentSparkCommentValidWithReferrer(uint64 sparksQuantity) public {
250
+ vm.assume(sparksQuantity > 0 && sparksQuantity < 1_000_000_000_000_000);
251
+
252
+ // comment
253
+ IComments.CommentIdentifier memory replyTo;
254
+ IComments.CommentIdentifier memory commentIdentifier = _mockComment(collectorWithToken, replyTo);
255
+
256
+ // mint
257
+ address commentor2 = makeAddr("commentor2");
258
+
259
+ uint256 zoraRecipientBalanceBeforeSpark = protocolRewards.balanceOf(zoraRecipient);
260
+
261
+ // spark comment
262
+ vm.deal(commentor2, sparksQuantity * SPARKS_VALUE);
263
+
264
+ address referrer = makeAddr("referrer");
265
+
266
+ vm.expectEmit(true, true, true, true);
267
+ emit IComments.SparkedComment(comments.hashCommentIdentifier(commentIdentifier), commentIdentifier, sparksQuantity, commentor2, block.timestamp);
268
+ vm.prank(commentor2);
269
+ comments.sparkComment{value: sparksQuantity * SPARKS_VALUE}(commentIdentifier, sparksQuantity, referrer);
270
+
271
+ uint256 zoraReward = (sparksQuantity * SPARKS_VALUE * ZORA_REWARD_PCT) / BPS_TO_PERCENT_2_DECIMAL_PERCISION;
272
+ uint256 referrerReward = (sparksQuantity * SPARKS_VALUE * REFERRER_REWARD_PCT) / BPS_TO_PERCENT_2_DECIMAL_PERCISION;
273
+ vm.assertEq(protocolRewards.balanceOf(zoraRecipient) - zoraRecipientBalanceBeforeSpark, zoraReward);
274
+ vm.assertEq(protocolRewards.balanceOf(referrer), referrerReward);
275
+ vm.assertEq(protocolRewards.balanceOf(collectorWithToken), (sparksQuantity * SPARKS_VALUE) - zoraReward - referrerReward);
276
+ }
277
+
278
+ function testCommentHashAndValidateCommentExists() public {
279
+ IComments.CommentIdentifier memory commentIdentifier = IComments.CommentIdentifier({
280
+ commenter: collectorWithToken,
281
+ contractAddress: address(mock1155),
282
+ tokenId: tokenId1,
283
+ nonce: keccak256("123456")
284
+ });
285
+ vm.expectRevert(abi.encodeWithSignature("CommentDoesntExist()"));
286
+ comments.hashAndValidateCommentExists(commentIdentifier);
287
+ }
288
+
289
+ function postComment(
290
+ address commenter,
291
+ address contractAddress,
292
+ uint256 tokenId,
293
+ string memory content,
294
+ IComments.CommentIdentifier memory replyTo
295
+ ) internal returns (IComments.CommentIdentifier memory) {
296
+ vm.prank(commenter);
297
+ return comments.comment{value: SPARKS_VALUE}(commenter, contractAddress, tokenId, content, replyTo, address(0));
298
+ }
299
+
300
+ function testHashAndCheckCommentExists() public {
301
+ address commenter = collectorWithToken;
302
+ address contractAddress = address(mock1155);
303
+ uint256 tokenId = tokenId1;
304
+
305
+ // Check that the comment doesn't exist initially
306
+ (bytes32 commentId, bool exists) = comments.hashAndCheckCommentExists(_expectedCommentIdentifier(contractAddress, tokenId, commenter));
307
+ assertFalse(exists);
308
+
309
+ // Setup and post comment
310
+ _setupCommenterWithTokenAndSparks(commenter, 1);
311
+ IComments.CommentIdentifier memory postedCommentIdentifier = postComment(commenter, contractAddress, tokenId, "test comment", emptyCommentIdentifier);
312
+
313
+ // Check that the comment now exists
314
+ (bytes32 newCommentId, bool newExists) = comments.hashAndCheckCommentExists(postedCommentIdentifier);
315
+ assertTrue(newExists);
316
+ assertEq(commentId, newCommentId);
317
+ assertEq(comments.hashCommentIdentifier(postedCommentIdentifier), newCommentId);
318
+ }
319
+
320
+ function testReplyToNonExistentComment() public {
321
+ address commentor = makeAddr("commentor");
322
+ _setupCommenterWithTokenAndSparks(commentor, 1);
323
+
324
+ IComments.CommentIdentifier memory nonExistentReplyTo = IComments.CommentIdentifier({
325
+ commenter: makeAddr("nonExistentCommenter"),
326
+ contractAddress: address(mock1155),
327
+ tokenId: tokenId1,
328
+ nonce: keccak256("nonExistentNonce")
329
+ });
330
+
331
+ vm.expectRevert(IComments.CommentDoesntExist.selector);
332
+ postComment(commentor, address(mock1155), tokenId1, "Replying to non-existent comment", nonExistentReplyTo);
333
+ }
334
+
335
+ function testReplyToCommentThatAddressDoesNotMatch() public {
336
+ address originalCommenter = makeAddr("originalCommenter");
337
+ address replier = makeAddr("replier");
338
+
339
+ _setupCommenterWithTokenAndSparks(originalCommenter, 1);
340
+ _setupCommenterWithTokenAndSparks(replier, 1);
341
+
342
+ IComments.CommentIdentifier memory originalCommentIdentifier = postComment(
343
+ originalCommenter,
344
+ address(mock1155),
345
+ tokenId1,
346
+ "Original comment",
347
+ emptyCommentIdentifier
348
+ );
349
+
350
+ // mismatched address
351
+ address mismatchedAddress = makeAddr("xyz");
352
+
353
+ vm.expectRevert(
354
+ abi.encodeWithSelector(IComments.CommentAddressOrTokenIdsDoNotMatch.selector, mismatchedAddress, tokenId1, address(mock1155), tokenId1)
355
+ );
356
+ vm.prank(replier);
357
+ comments.comment{value: SPARKS_VALUE}(replier, mismatchedAddress, tokenId1, "Reply to original comment", originalCommentIdentifier, address(0));
358
+
359
+ // mismatched tokenId
360
+ uint256 mismatchedTokenId = 123;
361
+
362
+ vm.expectRevert(
363
+ abi.encodeWithSelector(IComments.CommentAddressOrTokenIdsDoNotMatch.selector, address(mock1155), mismatchedTokenId, address(mock1155), tokenId1)
364
+ );
365
+ vm.prank(replier);
366
+ comments.comment{value: SPARKS_VALUE}(
367
+ replier,
368
+ address(mock1155),
369
+ mismatchedTokenId,
370
+ "Reply to original comment",
371
+ originalCommentIdentifier,
372
+ address(0)
373
+ );
374
+ }
375
+
376
+ function testReplyToExistingComment() public {
377
+ address originalCommenter = makeAddr("originalCommenter");
378
+ address replier = makeAddr("replier");
379
+
380
+ _setupCommenterWithTokenAndSparks(originalCommenter, 1);
381
+ _setupCommenterWithTokenAndSparks(replier, 1);
382
+
383
+ IComments.CommentIdentifier memory originalCommentIdentifier = postComment(
384
+ originalCommenter,
385
+ address(mock1155),
386
+ tokenId1,
387
+ "Original comment",
388
+ emptyCommentIdentifier
389
+ );
390
+
391
+ IComments.CommentIdentifier memory expectedReplyCommentIdentifier = _expectedCommentIdentifier(address(mock1155), tokenId1, replier);
392
+
393
+ vm.expectEmit(true, true, true, true);
394
+ emit IComments.Commented(
395
+ comments.hashCommentIdentifier(expectedReplyCommentIdentifier),
396
+ expectedReplyCommentIdentifier,
397
+ comments.hashCommentIdentifier(originalCommentIdentifier),
398
+ originalCommentIdentifier,
399
+ 1,
400
+ "Reply to original comment",
401
+ block.timestamp,
402
+ address(0)
403
+ );
404
+
405
+ IComments.CommentIdentifier memory replyCommentIdentifier = postComment(
406
+ replier,
407
+ address(mock1155),
408
+ tokenId1,
409
+ "Reply to original comment",
410
+ originalCommentIdentifier
411
+ );
412
+
413
+ assertEq(payable(address(comments)).balance, 0, "comments contract should have no balance");
414
+
415
+ uint64 commenteeSparks = comments.commentSparksQuantity(comments.hashCommentIdentifier(originalCommentIdentifier));
416
+ assertEq(commenteeSparks, 0, "commentee sparks should be 0");
417
+
418
+ assertEq(protocolRewards.balanceOf(originalCommenter), (SPARKS_VALUE * 70) / 100, "rewards mismatch");
419
+
420
+ (, bool exists) = comments.hashAndCheckCommentExists(replyCommentIdentifier);
421
+ assertTrue(exists);
422
+ }
423
+
424
+ function testCommentMismatchedCommenter() public {
425
+ address actualCommenter = makeAddr("actualCommenter");
426
+ address mismatchedCommenter = makeAddr("mismatchedCommenter");
427
+
428
+ _setupCommenterWithTokenAndSparks(actualCommenter, 1);
429
+
430
+ vm.expectRevert(abi.encodeWithSelector(IComments.CommenterMismatch.selector, mismatchedCommenter, actualCommenter));
431
+ vm.prank(actualCommenter);
432
+ comments.comment{value: SPARKS_VALUE}(mismatchedCommenter, address(mock1155), tokenId1, "Mismatched commenter", emptyCommentIdentifier, address(0));
433
+ }
434
+
435
+ function testRevertOnEmptyComment() public {
436
+ address commenter = makeAddr("commenter");
437
+ _setupCommenterWithTokenAndSparks(commenter, 1);
438
+
439
+ vm.expectRevert(IComments.EmptyComment.selector);
440
+ postComment(commenter, address(mock1155), tokenId1, "", emptyCommentIdentifier);
441
+ }
442
+
443
+ function testRevertOnNotTokenHolderOrAdmin() public {
444
+ address nonHolder = makeAddr("nonHolder");
445
+
446
+ // We don't set up the commenter with a token, so they're neither a holder nor an admin
447
+ vm.deal(nonHolder, SPARKS_VALUE);
448
+
449
+ vm.expectRevert(IComments.NotTokenHolderOrAdmin.selector);
450
+ postComment(nonHolder, address(mock1155), tokenId1, "Attempting to comment without holding token", emptyCommentIdentifier);
451
+ }
452
+
453
+ function testCommentRevertsWhenSendTooMuchValue() public {
454
+ address tokenHolder = makeAddr("tokenHolder");
455
+
456
+ _setupCommenterWithTokenAndSparks(tokenHolder, 1);
457
+
458
+ vm.deal(tokenHolder, 1 ether);
459
+ vm.prank(tokenHolder);
460
+ vm.expectRevert(abi.encodeWithSelector(IComments.IncorrectETHAmountForSparks.selector, 1 ether, SPARKS_VALUE));
461
+ comments.comment{value: 1 ether}(tokenHolder, address(mock1155), tokenId1, "test", emptyCommentIdentifier, address(0));
462
+ }
463
+
464
+ function testCommentRevertsWhenSendTooLittleValue() public {
465
+ address tokenHolder = makeAddr("tokenHolder");
466
+
467
+ _setupCommenterWithTokenAndSparks(tokenHolder, 1);
468
+
469
+ vm.prank(tokenHolder);
470
+ vm.expectRevert(abi.encodeWithSelector(IComments.MustSendAtLeastOneSpark.selector));
471
+ comments.comment(tokenHolder, address(mock1155), tokenId1, "test", emptyCommentIdentifier, address(0));
472
+ }
473
+
474
+ function testCommentRevertsWhenSendExactValue() public {
475
+ address tokenHolder = makeAddr("tokenHolder");
476
+
477
+ _setupCommenterWithTokenAndSparks(tokenHolder, 1);
478
+
479
+ vm.prank(tokenHolder);
480
+ comments.comment{value: SPARKS_VALUE}(tokenHolder, address(mock1155), tokenId1, "test", emptyCommentIdentifier, address(0));
481
+ }
482
+ }
@@ -0,0 +1,86 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.20;
3
+
4
+ import "forge-std/Test.sol";
5
+
6
+ import {CommentsImpl} from "../src/CommentsImpl.sol";
7
+ import {Comments} from "../src/proxy/Comments.sol";
8
+ import {IComments} from "../src/interfaces/IComments.sol";
9
+ import {Mock1155} from "./mocks/Mock1155.sol";
10
+ import {IProtocolRewards} from "@zoralabs/protocol-rewards/src/interfaces/IProtocolRewards.sol";
11
+ import {ProtocolRewards} from "./mocks/ProtocolRewards.sol";
12
+
13
+ contract CommentsTestBase is Test {
14
+ CommentsImpl internal comments;
15
+ Mock1155 internal mock1155;
16
+
17
+ uint256 internal constant SPARKS_VALUE = 0.000001 ether;
18
+
19
+ IComments.CommentIdentifier internal emptyCommentIdentifier;
20
+ ProtocolRewards internal protocolRewards;
21
+
22
+ address internal commentsAdmin = makeAddr("commentsAdmin");
23
+ address internal commentsBackfiller = makeAddr("commentsBackfiller");
24
+ address internal zoraRecipient = makeAddr("zoraRecipient");
25
+ address internal tokenAdmin = makeAddr("tokenCreator");
26
+ address internal collectorWithToken;
27
+ uint256 internal collectorWithTokenPrivateKey;
28
+ address internal collectorWithoutToken = makeAddr("collectorWithoutToken");
29
+ address internal sparker;
30
+ uint256 internal sparkerPrivateKey;
31
+
32
+ uint256 internal tokenId1 = 1;
33
+ uint256 internal tokenId2 = 2;
34
+
35
+ function setUp() public {
36
+ protocolRewards = new ProtocolRewards();
37
+ CommentsImpl commentsImpl = new CommentsImpl(SPARKS_VALUE, address(protocolRewards));
38
+
39
+ // initialze empty delegateCommenters array
40
+ address[] memory delegateCommenters = new address[](0);
41
+
42
+ // intialize proxy
43
+ comments = CommentsImpl(payable(address(new Comments(address(commentsImpl)))));
44
+ comments.initialize({
45
+ _zoraRecipient: zoraRecipient,
46
+ defaultAdmin: commentsAdmin,
47
+ backfiller: commentsBackfiller,
48
+ delegateCommenters: delegateCommenters
49
+ });
50
+
51
+ mock1155 = new Mock1155();
52
+
53
+ mock1155.createToken(tokenId1, tokenAdmin);
54
+ mock1155.createToken(tokenId2, tokenAdmin);
55
+
56
+ (collectorWithToken, collectorWithTokenPrivateKey) = makeAddrAndKey("collectorWithToken");
57
+ (sparker, sparkerPrivateKey) = makeAddrAndKey("sparker");
58
+
59
+ mock1155 = new Mock1155();
60
+
61
+ mock1155.createToken(tokenId1, tokenAdmin);
62
+ mock1155.createToken(tokenId2, tokenAdmin);
63
+ }
64
+
65
+ function _expectedCommentIdentifier(
66
+ address contractAddress,
67
+ uint256 tokenId,
68
+ address commenter
69
+ ) internal view returns (IComments.CommentIdentifier memory) {
70
+ return IComments.CommentIdentifier({commenter: commenter, contractAddress: contractAddress, tokenId: tokenId, nonce: comments.nextNonce()});
71
+ }
72
+
73
+ function _mockComment(
74
+ address commentor,
75
+ IComments.CommentIdentifier memory replyTo
76
+ ) internal returns (IComments.CommentIdentifier memory commentIdentifier) {
77
+ vm.startPrank(commentor);
78
+ mock1155.mint(commentor, tokenId1, 1, "");
79
+ vm.stopPrank();
80
+
81
+ vm.deal(commentor, SPARKS_VALUE);
82
+
83
+ vm.prank(commentor);
84
+ commentIdentifier = comments.comment{value: SPARKS_VALUE}(commentor, address(mock1155), tokenId1, "comment", replyTo, address(0));
85
+ }
86
+ }
@@ -0,0 +1,101 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.20;
3
+
4
+ import "forge-std/Test.sol";
5
+
6
+ import {CommentsImpl} from "../src/CommentsImpl.sol";
7
+ import {Comments} from "../src/proxy/Comments.sol";
8
+ import {IComments} from "../src/interfaces/IComments.sol";
9
+ import {Mock1155} from "./mocks/Mock1155.sol";
10
+ import {IProtocolRewards} from "@zoralabs/protocol-rewards/src/interfaces/IProtocolRewards.sol";
11
+ import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
12
+ import {MockMinter} from "./mocks/MockMinter.sol";
13
+
14
+ contract Comments_mintAndCommentTest is Test {
15
+ Mock1155 mock1155;
16
+ CommentsImpl comments;
17
+
18
+ uint256 constant SPARKS_VALUE = 0.000001 ether;
19
+
20
+ address zoraRecipient = makeAddr("zoraRecipient");
21
+ address commentsAdmin = makeAddr("commentsAdmin");
22
+ address commentor = makeAddr("commentor");
23
+ address tokenAdmin = makeAddr("tokenAdmin");
24
+
25
+ uint256 tokenId1 = 1;
26
+
27
+ address constant protocolRewards = 0x7777777F279eba3d3Ad8F4E708545291A6fDBA8B;
28
+ MockMinter mockMinter;
29
+
30
+ function setUp() public {
31
+ vm.createSelectFork("zora_sepolia", 14562731);
32
+
33
+ CommentsImpl commentsImpl = new CommentsImpl(SPARKS_VALUE, protocolRewards);
34
+
35
+ comments = CommentsImpl(payable(address(new Comments(address(commentsImpl)))));
36
+
37
+ // create mock minter, which will mint and comment on the comments contract.
38
+ // mock minter will be granted permission to do a delegateComment on the comments contract
39
+ mockMinter = new MockMinter(IComments(address(comments)));
40
+ address[] memory delegateCommenters = new address[](1);
41
+ delegateCommenters[0] = address(mockMinter);
42
+
43
+ comments.initialize({_zoraRecipient: zoraRecipient, defaultAdmin: commentsAdmin, backfiller: address(0), delegateCommenters: delegateCommenters});
44
+
45
+ mock1155 = new Mock1155();
46
+
47
+ mock1155.createToken(tokenId1, tokenAdmin);
48
+ }
49
+
50
+ function _expectedCommentIdentifier(
51
+ address commenter,
52
+ address contractAddress,
53
+ uint256 tokenId
54
+ ) internal view returns (IComments.CommentIdentifier memory) {
55
+ return IComments.CommentIdentifier({commenter: commenter, contractAddress: contractAddress, tokenId: tokenId, nonce: comments.nextNonce()});
56
+ }
57
+
58
+ function testCanMintAndCommentWithNoSparks() public {
59
+ uint256 quantityToMint = 1;
60
+ uint256 mintFee = 0.000111 ether;
61
+
62
+ address commenter = commentor;
63
+ address contractAddress = address(mock1155);
64
+ uint256 tokenId = tokenId1;
65
+
66
+ IComments.CommentIdentifier memory emptyReplyTo;
67
+
68
+ IComments.CommentIdentifier memory expectedCommentIdentifier = _expectedCommentIdentifier(commenter, contractAddress, tokenId);
69
+
70
+ bytes32 expectedCommentId = comments.hashCommentIdentifier(expectedCommentIdentifier);
71
+ bytes32 expectedReplyToId = bytes32(0);
72
+
73
+ uint64 sparksQuantity = 0;
74
+
75
+ vm.deal(commentor, mintFee * quantityToMint);
76
+ vm.expectEmit(true, true, true, true);
77
+ emit IComments.Commented(
78
+ expectedCommentId,
79
+ _expectedCommentIdentifier(commenter, contractAddress, tokenId),
80
+ expectedReplyToId,
81
+ emptyReplyTo,
82
+ sparksQuantity,
83
+ "test",
84
+ block.timestamp,
85
+ address(0)
86
+ );
87
+ vm.prank(commentor);
88
+ mockMinter.mintAndComment(quantityToMint, address(mock1155), tokenId1, "test");
89
+
90
+ // validate that the comment was created
91
+ (, bool exists) = comments.hashAndCheckCommentExists(expectedCommentIdentifier);
92
+ assertEq(exists, true);
93
+ }
94
+
95
+ function test_delegateComment_revertsWhenNotOwnerOrCreator() public {
96
+ address notOwner = makeAddr("notOwner");
97
+ vm.prank(notOwner);
98
+ vm.expectRevert(IComments.NotTokenHolderOrAdmin.selector);
99
+ mockMinter.forwardComment(address(mock1155), tokenId1, "test");
100
+ }
101
+ }