@zoralabs/comments-contracts 0.0.1 → 0.0.2

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 (110) hide show
  1. package/.turbo/turbo-build.log +49 -31
  2. package/README.md +10 -39
  3. package/abis/AddDelegateCommenterRole.json +9 -0
  4. package/abis/CallerAndCommenter.json +62 -0
  5. package/abis/CallerAndCommenterImpl.json +1218 -0
  6. package/abis/CallerAndCommenterMintAndCommentTest.json +771 -0
  7. package/abis/CallerAndCommenterSwapAndCommentTest.json +844 -0
  8. package/abis/CallerAndCommenterTestBase.json +577 -0
  9. package/abis/CommentsImpl.json +189 -59
  10. package/abis/CommentsImplConstants.json +106 -0
  11. package/abis/CommentsPermitTest.json +26 -6
  12. package/abis/CommentsTest.json +58 -10
  13. package/abis/Comments_mintAndCommentTest.json +11 -4
  14. package/abis/Comments_smartWallet.json +711 -0
  15. package/abis/DeployCallerAndCommenterImpl.json +9 -0
  16. package/abis/DeployImpl.json +0 -13
  17. package/abis/DeployNonDeterministic.json +0 -13
  18. package/abis/DeployScript.json +0 -13
  19. package/abis/EIP712Upgradeable.json +74 -0
  20. package/abis/EIP712UpgradeableWithChainId.json +49 -0
  21. package/abis/ERC20.json +310 -0
  22. package/abis/GenerateDeterministicParams.json +0 -13
  23. package/abis/ICallerAndCommenter.json +797 -0
  24. package/abis/IComments.json +629 -9
  25. package/abis/IERC20.json +39 -42
  26. package/abis/IERC20Metadata.json +224 -0
  27. package/abis/{CommentsDeployerBase.json → IMultiOwnable.json} +8 -2
  28. package/abis/IProtocolRewards.json +19 -0
  29. package/abis/ISecondarySwap.json +45 -0
  30. package/abis/IZoraCreator1155.json +51 -0
  31. package/abis/IZoraTimedSaleStrategy.json +91 -0
  32. package/abis/Mock1155.json +75 -1
  33. package/abis/Mock1155NoCreatorRewardRecipient.json +605 -0
  34. package/abis/Mock1155NoOwner.json +566 -0
  35. package/abis/{MockMinter.json → MockDelegateCommenter.json} +12 -2
  36. package/abis/MockERC20z.json +315 -0
  37. package/abis/MockMultiOwnable.json +212 -0
  38. package/abis/MockSecondarySwap.json +95 -0
  39. package/abis/MockZoraTimedSale.json +139 -0
  40. package/abis/Ownable2StepUpgradeable.json +138 -0
  41. package/abis/UnorderedNoncesUpgradeable.json +4 -4
  42. package/addresses/1.json +9 -0
  43. package/addresses/10.json +9 -0
  44. package/addresses/11155111.json +9 -0
  45. package/addresses/11155420.json +9 -0
  46. package/addresses/42161.json +9 -0
  47. package/addresses/7777777.json +9 -0
  48. package/addresses/81457.json +9 -0
  49. package/addresses/8453.json +9 -0
  50. package/addresses/84532.json +9 -0
  51. package/addresses/999999999.json +7 -2
  52. package/deterministicConfig/callerAndCommenter.json +8 -0
  53. package/deterministicConfig/comments.json +2 -2
  54. package/dist/index.cjs +724 -35
  55. package/dist/index.cjs.map +1 -1
  56. package/dist/index.js +723 -35
  57. package/dist/index.js.map +1 -1
  58. package/dist/types.d.ts +1 -1
  59. package/dist/types.d.ts.map +1 -1
  60. package/dist/wagmiGenerated.d.ts +1102 -57
  61. package/dist/wagmiGenerated.d.ts.map +1 -1
  62. package/package/types.ts +4 -1
  63. package/package/wagmiGenerated.ts +728 -32
  64. package/package.json +12 -11
  65. package/script/AddDelegateCommenterRole.s.sol +24 -0
  66. package/script/CommentsDeployerBase.sol +102 -19
  67. package/script/Deploy.s.sol +2 -44
  68. package/script/DeployCallerAndCommenterImpl.s.sol +29 -0
  69. package/script/DeployImpl.s.sol +1 -0
  70. package/script/DeployNonDeterministic.s.sol +22 -13
  71. package/script/GenerateDeterministicParams.s.sol +32 -4
  72. package/scripts/generateCommentsTestData.ts +170 -79
  73. package/src/CommentsImpl.sol +267 -134
  74. package/src/CommentsImplConstants.sol +44 -0
  75. package/src/interfaces/ICallerAndCommenter.sol +215 -0
  76. package/src/interfaces/IComments.sol +189 -42
  77. package/src/interfaces/IMultiOwnable.sol +10 -0
  78. package/src/interfaces/ISecondarySwap.sol +40 -0
  79. package/src/interfaces/IZoraCreator1155.sol +6 -1
  80. package/src/interfaces/IZoraCreator1155TypesV1.sol +46 -0
  81. package/src/interfaces/IZoraTimedSaleStrategy.sol +25 -0
  82. package/src/proxy/CallerAndCommenter.sol +43 -0
  83. package/src/utils/CallerAndCommenterImpl.sol +376 -0
  84. package/src/utils/EIP712UpgradeableWithChainId.sol +12 -23
  85. package/src/version/ContractVersionBase.sol +1 -1
  86. package/test/CallerAndCommenterTestBase.sol +77 -0
  87. package/test/CallerAndCommenter_mintAndComment.t copy.sol +214 -0
  88. package/test/CallerAndCommenter_swapAndComment.t.sol +523 -0
  89. package/test/Comments.t.sol +166 -29
  90. package/test/CommentsTestBase.sol +12 -20
  91. package/test/Comments_delegateComment.t.sol +129 -0
  92. package/test/Comments_permit.t.sol +131 -44
  93. package/test/Comments_smartWallet.t.sol +152 -0
  94. package/test/mocks/Mock1155.sol +12 -1
  95. package/test/mocks/Mock1155NoCreatorRewardRecipient.sol +65 -0
  96. package/test/mocks/Mock1155NoOwner.sol +53 -0
  97. package/test/mocks/MockDelegateCommenter.sol +36 -0
  98. package/test/mocks/MockIZoraCreator1155.sol +16 -0
  99. package/test/mocks/MockSecondarySwap.sol +30 -0
  100. package/test/mocks/MockZoraTimedSale.sol +38 -0
  101. package/wagmi.config.ts +3 -1
  102. package/abis/ProxyDeployerScript.json +0 -15
  103. package/scripts/backfillComments.ts +0 -176
  104. package/scripts/queries.ts +0 -73
  105. package/scripts/queryAndSaveComments.ts +0 -48
  106. package/scripts/queryQuantityOfComments.ts +0 -53
  107. package/scripts/writeComments.ts +0 -198
  108. package/src/deployments/CommentsDeployment.sol +0 -14
  109. package/test/Comments_mintAndComment.t.sol +0 -101
  110. package/test/mocks/MockMinter.sol +0 -29
@@ -1,11 +1,9 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.23;
3
3
 
4
- import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
5
4
  import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
6
5
  import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
7
6
  import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
8
- import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
9
7
  import {IHasContractName} from "@zoralabs/shared-contracts/interfaces/IContractMetadata.sol";
10
8
  import {ContractVersionBase} from "./version/ContractVersionBase.sol";
11
9
  import {IZoraCreator1155} from "./interfaces/IZoraCreator1155.sol";
@@ -14,40 +12,46 @@ import {IProtocolRewards} from "@zoralabs/protocol-rewards/src/interfaces/IProto
14
12
  import {UnorderedNoncesUpgradeable} from "@zoralabs/shared-contracts/utils/UnorderedNoncesUpgradeable.sol";
15
13
  import {EIP712UpgradeableWithChainId} from "./utils/EIP712UpgradeableWithChainId.sol";
16
14
  import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
15
+ import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
16
+ import {IMultiOwnable} from "./interfaces/IMultiOwnable.sol";
17
+ import {CommentsImplConstants} from "./CommentsImplConstants.sol";
17
18
 
18
19
  /// @title CommentsImpl
19
20
  /// @notice Contract for comments and sparking (liking with value) Zora 1155 posts.
20
21
  /// @dev Implements comment creation, sparking, and backfilling functionality. Implementation contract
21
22
  /// meant to be used with a UUPS upgradeable proxy contract.
22
- contract CommentsImpl is IComments, AccessControlUpgradeable, UUPSUpgradeable, ContractVersionBase, EIP712UpgradeableWithChainId, UnorderedNoncesUpgradeable {
23
- // this is the zora creator multisig that can upgrade the contract
24
- bytes32 public constant BACKFILLER_ROLE = keccak256("BACKFILLER_ROLE");
25
- // allows to delegate comment
26
- bytes32 public constant DELEGATE_COMMENTOR = keccak256("DELEGATE_COMMENTOR");
27
- uint256 public constant PERMISSION_BIT_ADMIN = 2 ** 1;
28
- uint256 public constant ZORA_REWARD_PCT = 10;
29
- uint256 public constant REFERRER_REWARD_PCT = 20;
30
- uint256 public constant ZORA_REWARD_NO_REFERRER_PCT = 30;
31
- uint256 internal constant BPS_TO_PERCENT_2_DECIMAL_PERCISION = 100;
32
-
33
- string public constant DOMAIN_NAME = "ZoraComments";
34
- string public constant DOMAIN_VERSION = "1";
23
+ /// @author oveddan / IsabellaSmallcombe
24
+ contract CommentsImpl is
25
+ IComments,
26
+ AccessControlUpgradeable,
27
+ UUPSUpgradeable,
28
+ ContractVersionBase,
29
+ EIP712UpgradeableWithChainId,
30
+ UnorderedNoncesUpgradeable,
31
+ CommentsImplConstants,
32
+ IHasContractName
33
+ {
34
+ /// @notice keccak256(abi.encode(uint256(keccak256("comments.storage.CommentsStorage")) - 1)) & ~bytes32(uint256(0xff))
35
+ bytes32 private constant COMMENTS_STORAGE_LOCATION = 0x9e5d0d3a4c7e8d5b9e8f9d9d5b9e8f9d9d5b9e8f9d9d5b9e8f9d9d5b9e8f9d00;
36
+ /// @notice the spark value to comment
37
+ uint256 public immutable sparkValue;
38
+ /// @notice the address of the protocol rewards contract
39
+ IProtocolRewards public immutable protocolRewards;
40
+ /// @notice account that receives rewards Zora Rewards for from a portion of the sparks value
41
+ address immutable zoraRecipient;
35
42
 
36
43
  /// @custom:storage-location erc7201:comments.storage.CommentsStorage
37
44
  struct CommentsStorage {
38
45
  mapping(bytes32 => Comment) comments;
39
- // account that receives rewards Zora Rewards for from a portion of the sparks value
40
- address zoraRecipient;
46
+ // gap that held old zora recipient.
47
+ address __gap;
41
48
  // Global autoincrementing nonce
42
49
  uint256 nonce;
43
50
  }
44
51
 
45
- // keccak256(abi.encode(uint256(keccak256("comments.storage.CommentsStorage")) - 1)) & ~bytes32(uint256(0xff))
46
- bytes32 private constant CommentsStorageLocation = 0x9e5d0d3a4c7e8d5b9e8f9d9d5b9e8f9d9d5b9e8f9d9d5b9e8f9d9d5b9e8f9d00;
47
-
48
52
  function _getCommentsStorage() private pure returns (CommentsStorage storage $) {
49
53
  assembly {
50
- $.slot := CommentsStorageLocation
54
+ $.slot := COMMENTS_STORAGE_LOCATION
51
55
  }
52
56
  }
53
57
 
@@ -55,38 +59,57 @@ contract CommentsImpl is IComments, AccessControlUpgradeable, UUPSUpgradeable, C
55
59
  return _getCommentsStorage().comments[commentId];
56
60
  }
57
61
 
58
- function commentSparksQuantity(bytes32 commentId) external view returns (uint64) {
59
- return comments(commentId).totalSparks;
62
+ /// @notice Returns the total number of sparks a given comment has received
63
+ /// @param commentIdentifier The identifier of the comment
64
+ /// @return The total number of sparks a comment has received
65
+ function commentSparksQuantity(CommentIdentifier memory commentIdentifier) external view returns (uint256) {
66
+ return comments(hashCommentIdentifier(commentIdentifier)).totalSparks;
60
67
  }
61
68
 
69
+ /// @notice Returns the next nonce for comment creation
70
+ /// @return The next nonce
62
71
  function nextNonce() external view returns (bytes32) {
63
72
  return bytes32(_getCommentsStorage().nonce);
64
73
  }
65
74
 
66
- uint256 public immutable sparkValue;
67
-
68
- IProtocolRewards public immutable protocolRewards;
75
+ /// @notice Returns the implementation address of the contract
76
+ /// @return The implementation address
77
+ function implementation() external view returns (address) {
78
+ return ERC1967Utils.getImplementation();
79
+ }
69
80
 
70
- // the sparks value could also be a comment
71
- constructor(uint256 _sparkValue, address _protocolRewards) {
81
+ /// @notice Contract constructor
82
+ /// @param _sparkValue The value of a spark
83
+ /// @param _protocolRewards The address of the protocol rewards contract
84
+ /// @param _zoraRecipient The address of the zora recipient
85
+ constructor(uint256 _sparkValue, address _protocolRewards, address _zoraRecipient) {
86
+ if (_protocolRewards == address(0) || _zoraRecipient == address(0)) {
87
+ revert AddressZero();
88
+ }
72
89
  _disableInitializers();
73
90
 
74
91
  sparkValue = _sparkValue;
75
92
  protocolRewards = IProtocolRewards(_protocolRewards);
93
+ zoraRecipient = _zoraRecipient;
76
94
  }
77
95
 
78
- function initialize(address _zoraRecipient, address defaultAdmin, address backfiller, address[] calldata delegateCommenters) public initializer {
96
+ /// @notice Initializes the contract with default admin, backfiller, and delegate commenters
97
+ /// @param defaultAdmin The address of the default admin
98
+ /// @param backfiller The address of the backfiller
99
+ /// @param delegateCommenters The addresses of the delegate commenters
100
+ function initialize(address defaultAdmin, address backfiller, address[] calldata delegateCommenters) public initializer {
101
+ if (defaultAdmin == address(0) || backfiller == address(0)) {
102
+ revert AddressZero();
103
+ }
79
104
  __AccessControl_init();
80
105
  __UUPSUpgradeable_init();
81
106
  __EIP712_init(DOMAIN_NAME, DOMAIN_VERSION);
82
107
 
83
- _getCommentsStorage().zoraRecipient = _zoraRecipient;
84
-
85
108
  _grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);
86
109
  _grantRole(BACKFILLER_ROLE, backfiller);
87
110
 
88
111
  for (uint256 i = 0; i < delegateCommenters.length; i++) {
89
- _grantRole(DELEGATE_COMMENTOR, delegateCommenters[i]);
112
+ _grantRole(DELEGATE_COMMENTER, delegateCommenters[i]);
90
113
  }
91
114
  }
92
115
 
@@ -124,6 +147,8 @@ contract CommentsImpl is IComments, AccessControlUpgradeable, UUPSUpgradeable, C
124
147
  /// @param commenter The address of the commenter
125
148
  /// @param text The text content of the comment
126
149
  /// @param replyTo The identifier of the comment being replied to (if any)
150
+ /// @param commenterSmartWallet If the commenter has a smart wallet, the smart wallet, which can checked to be the owner or creator of the 1155 token
151
+ /// @param referrer The address of the referrer (if any)
127
152
  /// @return commentIdentifier The identifier of the created comment, including the nonce
128
153
  function comment(
129
154
  address commenter,
@@ -131,31 +156,71 @@ contract CommentsImpl is IComments, AccessControlUpgradeable, UUPSUpgradeable, C
131
156
  uint256 tokenId,
132
157
  string calldata text,
133
158
  CommentIdentifier calldata replyTo,
159
+ address commenterSmartWallet,
134
160
  address referrer
135
161
  ) external payable returns (CommentIdentifier memory commentIdentifier) {
136
- uint64 sparksQuantity = 0;
137
- if (msg.value != 0) {
138
- sparksQuantity = 1;
139
- }
140
- _validateSparksQuantityMatchesValue(sparksQuantity, msg.value);
162
+ uint256 sparksQuantity = _getAndValidateSingleSparkQuantityFromValue(msg.value);
141
163
 
142
164
  commentIdentifier = _createCommentIdentifier(contractAddress, tokenId, commenter);
143
165
 
144
- _comment(msg.sender, commentIdentifier, text, sparksQuantity, replyTo, referrer, true);
166
+ _comment({
167
+ commenter: msg.sender,
168
+ commentIdentifier: commentIdentifier,
169
+ text: text,
170
+ sparksQuantity: sparksQuantity,
171
+ replyTo: replyTo,
172
+ commenterSmartWallet: commenterSmartWallet,
173
+ referrer: referrer,
174
+ mustSendAtLeastOneSpark: true
175
+ });
176
+ }
177
+
178
+ // gets the sparks quantity from the value sent with the transaction,
179
+ // ensuring that at most 1 spark is sent.
180
+ function _getAndValidateSingleSparkQuantityFromValue(uint256 value) internal view returns (uint256) {
181
+ if (value == 0) {
182
+ return 0;
183
+ }
184
+ if (value != sparkValue) {
185
+ revert IncorrectETHAmountForSparks(value, sparkValue);
186
+ }
187
+ return 1;
145
188
  }
146
189
 
147
- // allows another contract to call this function to signify a caller commented, and is trusted
148
- // to provide who the original commentor was. also allows no sparks to be sent.
190
+ // Allows another contract to call this function to signify a caller commented, and is trusted
191
+ // to provide who the original commenter was. Allows no sparks to be sent.
192
+ /// @notice Allows another contract to delegate comment creation on behalf of a user
193
+ /// @param commenter The address of the commenter
194
+ /// @param contractAddress The address of the contract
195
+ /// @param tokenId The token ID
196
+ /// @param text The text content of the comment
197
+ /// @param replyTo The identifier of the comment being replied to (if any)
198
+ /// @param referrer The address of the referrer (if any)
199
+ /// @param commenterSmartWalletOwner If the commenter has a smart wallet, the smart wallet owner address
200
+ /// @return commentIdentifier The identifier of the created comment, including the nonce
149
201
  function delegateComment(
150
202
  address commenter,
151
203
  address contractAddress,
152
204
  uint256 tokenId,
153
205
  string calldata text,
154
- CommentIdentifier calldata replyTo
155
- ) external onlyRole(DELEGATE_COMMENTOR) returns (CommentIdentifier memory commentIdentifier) {
206
+ CommentIdentifier calldata replyTo,
207
+ address commenterSmartWalletOwner,
208
+ address referrer
209
+ ) external payable onlyRole(DELEGATE_COMMENTER) returns (CommentIdentifier memory commentIdentifier, bytes32 commentId) {
210
+ uint256 sparksQuantity = _getAndValidateSingleSparkQuantityFromValue(msg.value);
211
+
156
212
  commentIdentifier = _createCommentIdentifier(contractAddress, tokenId, commenter);
157
213
 
158
- _comment(commentIdentifier.commenter, commentIdentifier, text, 0, replyTo, address(0), false);
214
+ commentId = _comment({
215
+ commenter: commentIdentifier.commenter,
216
+ commentIdentifier: commentIdentifier,
217
+ text: text,
218
+ sparksQuantity: sparksQuantity,
219
+ replyTo: replyTo,
220
+ commenterSmartWallet: commenterSmartWalletOwner,
221
+ referrer: referrer,
222
+ mustSendAtLeastOneSpark: false
223
+ });
159
224
  }
160
225
 
161
226
  function _createCommentIdentifier(address contractAddress, uint256 tokenId, address commenter) private returns (CommentIdentifier memory) {
@@ -167,8 +232,9 @@ contract CommentsImpl is IComments, AccessControlUpgradeable, UUPSUpgradeable, C
167
232
  address commenter,
168
233
  CommentIdentifier memory commentIdentifier,
169
234
  string memory text,
170
- uint64 sparksQuantity,
235
+ uint256 sparksQuantity,
171
236
  CommentIdentifier memory replyTo,
237
+ address commenterSmartWallet,
172
238
  address referrer,
173
239
  bool mustSendAtLeastOneSpark
174
240
  ) internal returns (bytes32) {
@@ -176,7 +242,14 @@ contract CommentsImpl is IComments, AccessControlUpgradeable, UUPSUpgradeable, C
176
242
  revert CommenterMismatch(commentIdentifier.commenter, commenter);
177
243
  }
178
244
 
179
- (bytes32 commentId, bytes32 replyToId) = _validateComment(commentIdentifier, replyTo, text, sparksQuantity, mustSendAtLeastOneSpark);
245
+ (bytes32 commentId, bytes32 replyToId) = _validateComment(
246
+ commentIdentifier,
247
+ replyTo,
248
+ text,
249
+ sparksQuantity,
250
+ commenterSmartWallet,
251
+ mustSendAtLeastOneSpark
252
+ );
180
253
 
181
254
  _saveCommentAndTransferSparks(commentId, commentIdentifier, text, sparksQuantity, replyToId, replyTo, block.timestamp, referrer);
182
255
 
@@ -193,7 +266,8 @@ contract CommentsImpl is IComments, AccessControlUpgradeable, UUPSUpgradeable, C
193
266
  CommentIdentifier memory commentIdentifier,
194
267
  CommentIdentifier memory replyTo,
195
268
  string memory text,
196
- uint64 sparksQuantity,
269
+ uint256 sparksQuantity,
270
+ address commenterSmartWallet,
197
271
  bool mustSendAtLeastOneSpark
198
272
  ) internal view returns (bytes32 commentId, bytes32 replyToId) {
199
273
  // verify that the commenter specified in the identifier is the one expected
@@ -208,20 +282,35 @@ contract CommentsImpl is IComments, AccessControlUpgradeable, UUPSUpgradeable, C
208
282
  revert EmptyComment();
209
283
  }
210
284
 
211
- _validateCommentorAndSparksQuantity(commentIdentifier, sparksQuantity, mustSendAtLeastOneSpark);
285
+ _validateCommenterAndSparksQuantity(commentIdentifier, sparksQuantity, mustSendAtLeastOneSpark, commenterSmartWallet);
212
286
  }
213
287
 
214
- function _validateCommentorAndSparksQuantity(
288
+ function _validateCommenterAndSparksQuantity(
215
289
  CommentIdentifier memory commentIdentifier,
216
- uint64 sparksQuantity,
217
- bool mustSendAtLeastOneSpark
290
+ uint256 sparksQuantity,
291
+ bool mustSendAtLeastOneSpark,
292
+ address commenterSmartWallet
218
293
  ) internal view {
219
- // check that the commenter is a token admin - if they are, then they can comment for free
220
- if (_isTokenAdmin(commentIdentifier.contractAddress, commentIdentifier.tokenId, commentIdentifier.commenter)) {
294
+ if (commenterSmartWallet != address(0)) {
295
+ if (commenterSmartWallet.code.length == 0) {
296
+ revert NotSmartWallet();
297
+ }
298
+ // check if the commenter is a smart wallet owner
299
+ if (!IMultiOwnable(commenterSmartWallet).isOwnerAddress(commentIdentifier.commenter)) {
300
+ revert NotSmartWalletOwner();
301
+ }
302
+ }
303
+
304
+ // check that the commenter or smart wallet is a token admin - if they are, then they can comment for free
305
+ if (
306
+ _accountOrSmartWalletIsTokenAdmin(commentIdentifier.contractAddress, commentIdentifier.tokenId, commentIdentifier.commenter, commenterSmartWallet)
307
+ ) {
221
308
  return;
222
309
  }
223
- // if they aren't, then they must be a token holder, and have included at least 1 spark
224
- if (!_isTokenHolder(commentIdentifier.contractAddress, commentIdentifier.tokenId, commentIdentifier.commenter)) {
310
+ // if they aren't, commenter or smart wallet must be a token holder, and have included at least 1 spark
311
+ if (
312
+ !_accountOrSmartWalletIsTokenHolder(commentIdentifier.contractAddress, commentIdentifier.tokenId, commentIdentifier.commenter, commenterSmartWallet)
313
+ ) {
225
314
  revert NotTokenHolderOrAdmin();
226
315
  }
227
316
 
@@ -230,10 +319,6 @@ contract CommentsImpl is IComments, AccessControlUpgradeable, UUPSUpgradeable, C
230
319
  }
231
320
  }
232
321
 
233
- bytes4 constant zoraRewardReason = bytes4(keccak256("zoraRewardForCommentDeposited()"));
234
- bytes4 constant referrerRewardReason = bytes4(keccak256("referrerRewardForCommentDeposited()"));
235
- bytes4 constant sparksRecipientRewardReason = bytes4(keccak256("sparksRecipientRewardForCommentDeposited()"));
236
-
237
322
  function _getRewardDeposits(
238
323
  address sparksRecipient,
239
324
  address referrer,
@@ -244,33 +329,31 @@ contract CommentsImpl is IComments, AccessControlUpgradeable, UUPSUpgradeable, C
244
329
  uint256[] memory amounts = new uint256[](recipientCount);
245
330
  bytes4[] memory reasons = new bytes4[](recipientCount);
246
331
 
247
- address zoraRecipient = _getCommentsStorage().zoraRecipient;
248
-
249
332
  if (referrer != address(0)) {
250
333
  uint256 zoraReward = (ZORA_REWARD_PCT * sparksValue) / BPS_TO_PERCENT_2_DECIMAL_PERCISION;
251
334
  recipients[0] = zoraRecipient;
252
335
  amounts[0] = zoraReward;
253
- reasons[0] = zoraRewardReason;
336
+ reasons[0] = ZORA_REWARD_REASON;
254
337
 
255
338
  uint256 referrerReward = (REFERRER_REWARD_PCT * sparksValue) / BPS_TO_PERCENT_2_DECIMAL_PERCISION;
256
339
  recipients[1] = referrer;
257
340
  amounts[1] = referrerReward;
258
- reasons[1] = referrerRewardReason;
341
+ reasons[1] = REFERRER_REWARD_REASON;
259
342
 
260
343
  uint256 sparksRecipientReward = sparksValue - zoraReward - referrerReward;
261
344
  recipients[2] = sparksRecipient;
262
345
  amounts[2] = sparksRecipientReward;
263
- reasons[2] = sparksRecipientRewardReason;
346
+ reasons[2] = SPARKS_RECIPIENT_REWARD_REASON;
264
347
  } else {
265
348
  uint256 zoraRewardNoReferrer = (ZORA_REWARD_NO_REFERRER_PCT * sparksValue) / BPS_TO_PERCENT_2_DECIMAL_PERCISION;
266
349
  recipients[0] = zoraRecipient;
267
350
  amounts[0] = zoraRewardNoReferrer;
268
- reasons[0] = zoraRewardReason;
351
+ reasons[0] = ZORA_REWARD_REASON;
269
352
 
270
353
  uint256 sparkRecipientReward = sparksValue - zoraRewardNoReferrer;
271
354
  recipients[1] = sparksRecipient;
272
355
  amounts[1] = sparkRecipientReward;
273
- reasons[1] = sparksRecipientRewardReason;
356
+ reasons[1] = SPARKS_RECIPIENT_REWARD_REASON;
274
357
  }
275
358
 
276
359
  return (recipients, amounts, reasons);
@@ -281,8 +364,27 @@ contract CommentsImpl is IComments, AccessControlUpgradeable, UUPSUpgradeable, C
281
364
  protocolRewards.depositBatch{value: sparksValue}(recipients, amounts, reasons, depositBatchComment);
282
365
  }
283
366
 
367
+ function _accountOrSmartWalletIsTokenAdmin(address contractAddress, uint256 tokenId, address user, address smartWallet) internal view returns (bool) {
368
+ if (_isTokenAdmin(contractAddress, tokenId, user)) {
369
+ return true;
370
+ }
371
+ if (smartWallet != address(0)) {
372
+ return _isTokenAdmin(contractAddress, tokenId, smartWallet);
373
+ }
374
+ return false;
375
+ }
376
+
377
+ function _accountOrSmartWalletIsTokenHolder(address contractAddress, uint256 tokenId, address user, address smartWallet) internal view returns (bool) {
378
+ if (_isTokenHolder(contractAddress, tokenId, user)) {
379
+ return true;
380
+ }
381
+ if (smartWallet != address(0)) {
382
+ return _isTokenHolder(contractAddress, tokenId, smartWallet);
383
+ }
384
+ return false;
385
+ }
386
+
284
387
  function _isTokenAdmin(address contractAddress, uint256 tokenId, address user) internal view returns (bool) {
285
- // TOOD: investigate is there a better way to know the token creator?
286
388
  return IZoraCreator1155(contractAddress).isAdminOrRole(user, tokenId, PERMISSION_BIT_ADMIN);
287
389
  }
288
390
 
@@ -306,7 +408,7 @@ contract CommentsImpl is IComments, AccessControlUpgradeable, UUPSUpgradeable, C
306
408
  bytes32 commentId,
307
409
  CommentIdentifier memory commentIdentifier,
308
410
  string memory text,
309
- uint64 sparksQuantity,
411
+ uint256 sparksQuantity,
310
412
  bytes32 replyToId,
311
413
  CommentIdentifier memory replyToIdentifier,
312
414
  uint256 timestamp,
@@ -330,7 +432,7 @@ contract CommentsImpl is IComments, AccessControlUpgradeable, UUPSUpgradeable, C
330
432
  bytes32 commentId,
331
433
  CommentIdentifier memory commentIdentifier,
332
434
  string memory text,
333
- uint64 sparksQuantity,
435
+ uint256 sparksQuantity,
334
436
  bytes32 replyToId,
335
437
  CommentIdentifier memory replyToIdentifier,
336
438
  uint256 timestamp,
@@ -345,11 +447,11 @@ contract CommentsImpl is IComments, AccessControlUpgradeable, UUPSUpgradeable, C
345
447
  }
346
448
 
347
449
  /// @notice Sparks a comment. Equivalant sparks value in eth to sparksQuantity must be sent with the transaction. Sparking a comment is
348
- /// similar to liking it, except it is liked with the value of sparks attached. The spark value gets sent to the commentor, with a fee taken out.
450
+ /// similar to liking it, except it is liked with the value of sparks attached. The spark value gets sent to the commenter, with a fee taken out.
349
451
  /// @param commentIdentifier The identifier of the comment to spark
350
452
  /// @param sparksQuantity The quantity of sparks to send
351
453
  /// @param referrer The referrer of the comment
352
- function sparkComment(CommentIdentifier calldata commentIdentifier, uint64 sparksQuantity, address referrer) public payable {
454
+ function sparkComment(CommentIdentifier calldata commentIdentifier, uint256 sparksQuantity, address referrer) public payable {
353
455
  if (sparksQuantity == 0) {
354
456
  revert MustSendAtLeastOneSpark();
355
457
  }
@@ -357,13 +459,13 @@ contract CommentsImpl is IComments, AccessControlUpgradeable, UUPSUpgradeable, C
357
459
  _sparkComment(commentIdentifier, msg.sender, sparksQuantity, referrer);
358
460
  }
359
461
 
360
- function _validateSparksQuantityMatchesValue(uint64 sparksQuantity, uint256 value) internal view {
462
+ function _validateSparksQuantityMatchesValue(uint256 sparksQuantity, uint256 value) internal view {
361
463
  if (value != sparksQuantity * sparkValue) {
362
464
  revert IncorrectETHAmountForSparks(value, sparksQuantity * sparkValue);
363
465
  }
364
466
  }
365
467
 
366
- function _sparkComment(CommentIdentifier memory commentIdentifier, address sparker, uint64 sparksQuantity, address referrer) internal {
468
+ function _sparkComment(CommentIdentifier memory commentIdentifier, address sparker, uint256 sparksQuantity, address referrer) internal {
367
469
  if (sparker == commentIdentifier.commenter) {
368
470
  revert CannotSparkOwnComment();
369
471
  }
@@ -372,20 +474,13 @@ contract CommentsImpl is IComments, AccessControlUpgradeable, UUPSUpgradeable, C
372
474
  revert CommentDoesntExist();
373
475
  }
374
476
 
375
- comments(commentId).totalSparks += uint64(sparksQuantity);
477
+ comments(commentId).totalSparks += uint256(sparksQuantity);
376
478
 
377
479
  _transferSparksValueToRecipient(commentIdentifier.commenter, referrer, sparksQuantity * sparkValue, "Sparked Comment");
378
480
 
379
- emit SparkedComment(commentId, commentIdentifier, sparksQuantity, sparker, block.timestamp);
481
+ emit SparkedComment(commentId, commentIdentifier, sparksQuantity, sparker, block.timestamp, referrer);
380
482
  }
381
483
 
382
- bytes32 constant PERMIT_COMMENT_DOMAIN =
383
- keccak256(
384
- "PermitComment(address contractAddress,uint256 tokenId,address commenter,CommentIdentifier replyTo,bytes text,uint64 sparksQuantity,uint256 deadline,bytes32 nonce,address referrer,uint256 sourceChainId,uint256 destinationChainId)CommentIdentifier(address contractAddress,uint256 tokenId,address commenter,bytes32 nonce)"
385
- );
386
-
387
- bytes32 constant COMMENT_IDENTIFIER_DOMAIN = keccak256("CommentIdentifier(address contractAddress,uint256 tokenId,address commenter,bytes32 nonce)");
388
-
389
484
  function _hashCommentIdentifier(CommentIdentifier memory commentIdentifier) internal pure returns (bytes32) {
390
485
  return
391
486
  keccak256(
@@ -399,29 +494,28 @@ contract CommentsImpl is IComments, AccessControlUpgradeable, UUPSUpgradeable, C
399
494
  );
400
495
  }
401
496
 
402
- function _hashTypedDataV4WithCustomChain(bytes32 permitDomain, bytes memory permitData, uint256 sourceChainId) internal view returns (bytes32) {
403
- return _hashTypedDataV4(keccak256(abi.encode(permitDomain, permitData)), sourceChainId);
404
- }
405
-
497
+ /// @notice Hashes a permit comment struct for signing
498
+ /// @param permit The permit comment struct
499
+ /// @return The hash to sign
406
500
  function hashPermitComment(PermitComment calldata permit) public view returns (bytes32) {
407
- return
408
- _hashTypedDataV4WithCustomChain(
501
+ bytes32 structHash = keccak256(
502
+ abi.encode(
409
503
  PERMIT_COMMENT_DOMAIN,
410
- abi.encode(
411
- permit.contractAddress,
412
- permit.tokenId,
413
- permit.commenter,
414
- _hashCommentIdentifier(permit.replyTo),
415
- keccak256(bytes(permit.text)),
416
- permit.sparksQuantity,
417
- permit.deadline,
418
- permit.nonce,
419
- permit.referrer,
420
- permit.sourceChainId,
421
- permit.destinationChainId
422
- ),
423
- permit.sourceChainId
424
- );
504
+ permit.contractAddress,
505
+ permit.tokenId,
506
+ permit.commenter,
507
+ _hashCommentIdentifier(permit.replyTo),
508
+ keccak256(bytes(permit.text)),
509
+ permit.deadline,
510
+ permit.nonce,
511
+ permit.commenterSmartWallet,
512
+ permit.referrer,
513
+ permit.sourceChainId,
514
+ permit.destinationChainId
515
+ )
516
+ );
517
+
518
+ return _hashTypedDataV4(structHash, permit.sourceChainId);
425
519
  }
426
520
 
427
521
  function _validatePermit(bytes32 digest, bytes32 nonce, bytes calldata signature, address signer, uint256 deadline) internal {
@@ -429,12 +523,16 @@ contract CommentsImpl is IComments, AccessControlUpgradeable, UUPSUpgradeable, C
429
523
  revert ERC2612ExpiredSignature(deadline);
430
524
  }
431
525
 
432
- _useCheckedNonce(signer, uint256(nonce));
526
+ _useCheckedNonce(signer, nonce);
433
527
  _validateSignerIsCommenter(digest, signature, signer);
434
528
  }
435
529
 
530
+ /// @notice Creates a comment on behalf of another account using a signed message. Supports cross-chain permits
531
+ /// by specifying the source and destination chain ids. The signature must be signed by the commenter on the source chain.
532
+ /// @param permit The permit that was signed off-chain on the source chain
533
+ /// @param signature The signature of the permit comment
436
534
  function permitComment(PermitComment calldata permit, bytes calldata signature) public payable {
437
- if (permit.destinationChainId != block.chainid) {
535
+ if (permit.destinationChainId != uint32(block.chainid)) {
438
536
  revert IncorrectDestinationChain(permit.destinationChainId);
439
537
  }
440
538
 
@@ -442,45 +540,46 @@ contract CommentsImpl is IComments, AccessControlUpgradeable, UUPSUpgradeable, C
442
540
  _validatePermit(digest, permit.nonce, signature, permit.commenter, permit.deadline);
443
541
 
444
542
  CommentIdentifier memory commentIdentifier = _createCommentIdentifier(permit.contractAddress, permit.tokenId, permit.commenter);
445
- (bytes32 commentId, bytes32 replyToId) = _validateComment(commentIdentifier, permit.replyTo, permit.text, permit.sparksQuantity, true);
446
543
 
447
- _saveCommentAndTransferSparks(
448
- commentId,
544
+ uint256 sparksQuantity = _getAndValidateSingleSparkQuantityFromValue(msg.value);
545
+
546
+ (bytes32 commentId, bytes32 replyToId) = _validateComment(
449
547
  commentIdentifier,
450
- permit.text,
451
- permit.sparksQuantity,
452
- replyToId,
453
548
  permit.replyTo,
454
- block.timestamp,
455
- permit.referrer
549
+ permit.text,
550
+ sparksQuantity,
551
+ permit.commenterSmartWallet,
552
+ true
456
553
  );
457
- }
458
554
 
459
- bytes32 constant PERMIT_SPARK_COMMENT_DOMAIN =
460
- keccak256(
461
- "PermitSparkComment(CommentIdentifier comment,address sparker,uint64 sparksQuantity,uint256 deadline,bytes32 nonce,address referrer,uint256 sourceChainId,uint256 destinationChainId)CommentIdentifier(address contractAddress,uint256 tokenId,address commenter,bytes32 nonce)"
462
- );
555
+ _saveCommentAndTransferSparks(commentId, commentIdentifier, permit.text, sparksQuantity, replyToId, permit.replyTo, block.timestamp, permit.referrer);
556
+ }
463
557
 
558
+ /// @notice Hashes a permit spark comment struct for signing
559
+ /// @param permit The permit spark comment struct
464
560
  function hashPermitSparkComment(PermitSparkComment calldata permit) public view returns (bytes32) {
465
- return
466
- _hashTypedDataV4WithCustomChain(
561
+ bytes32 structHash = keccak256(
562
+ abi.encode(
467
563
  PERMIT_SPARK_COMMENT_DOMAIN,
468
- abi.encode(
469
- _hashCommentIdentifier(permit.comment),
470
- permit.sparker,
471
- permit.sparksQuantity,
472
- permit.deadline,
473
- permit.nonce,
474
- permit.referrer,
475
- permit.sourceChainId,
476
- permit.destinationChainId
477
- ),
478
- permit.sourceChainId
479
- );
564
+ _hashCommentIdentifier(permit.comment),
565
+ permit.sparker,
566
+ permit.sparksQuantity,
567
+ permit.deadline,
568
+ permit.nonce,
569
+ permit.referrer,
570
+ permit.sourceChainId,
571
+ permit.destinationChainId
572
+ )
573
+ );
574
+ return _hashTypedDataV4(structHash, permit.sourceChainId);
480
575
  }
481
576
 
577
+ /// @notice Sparks a comment on behalf of another account using a signed message. Supports cross-chain permits
578
+ /// by specifying the source and destination chain ids. The signature must be signed by the sparker on the source chain.
579
+ /// @param permit The permit spark comment struct
580
+ /// @param signature The signature of the permit. Must be signed by the sparker.
482
581
  function permitSparkComment(PermitSparkComment calldata permit, bytes calldata signature) public payable {
483
- if (permit.destinationChainId != block.chainid) {
582
+ if (permit.destinationChainId != uint32(block.chainid)) {
484
583
  revert IncorrectDestinationChain(permit.destinationChainId);
485
584
  }
486
585
 
@@ -530,11 +629,45 @@ contract CommentsImpl is IComments, AccessControlUpgradeable, UUPSUpgradeable, C
530
629
  }
531
630
  }
532
631
 
632
+ function _getFundsRecipient(address contractAddress) internal view returns (address) {
633
+ try IZoraCreator1155(contractAddress).config() returns (address, uint96, address payable fundsRecipient, uint96, address, uint96) {
634
+ if (fundsRecipient != address(0)) {
635
+ return fundsRecipient;
636
+ }
637
+ } catch {}
638
+
639
+ try IZoraCreator1155(contractAddress).owner() returns (address owner) {
640
+ if (owner != address(0)) {
641
+ return owner;
642
+ }
643
+ } catch {}
644
+
645
+ return address(0);
646
+ }
647
+
648
+ function _tryGetCreatorRewardRecipient(CommentIdentifier memory commentIdentifier) internal view returns (address) {
649
+ try IZoraCreator1155(commentIdentifier.contractAddress).getCreatorRewardRecipient(commentIdentifier.tokenId) returns (address creatorRecipient) {
650
+ return creatorRecipient;
651
+ } catch {
652
+ return address(0);
653
+ }
654
+ }
655
+
533
656
  function _getCreatorRewardRecipient(CommentIdentifier memory commentIdentifier) internal view returns (address) {
534
- // TODO: Implement logic to get creator reward recipient
535
- return IZoraCreator1155(commentIdentifier.contractAddress).getCreatorRewardRecipient(commentIdentifier.tokenId);
657
+ address creatorRecipient = _tryGetCreatorRewardRecipient(commentIdentifier);
658
+ if (creatorRecipient != address(0)) {
659
+ return creatorRecipient;
660
+ }
661
+
662
+ address fundsRecipient = _getFundsRecipient(commentIdentifier.contractAddress);
663
+ if (fundsRecipient != address(0)) {
664
+ return fundsRecipient;
665
+ }
666
+ revert NoFundsRecipient();
536
667
  }
537
668
 
669
+ /// @notice Returns the name of the contract
670
+ /// @return The name of the contract
538
671
  function contractName() public pure returns (string memory) {
539
672
  return "Zora Comments";
540
673
  }