@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
@@ -0,0 +1,46 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.17;
3
+
4
+ /*
5
+
6
+
7
+ ░░░░░░░░░░░░░░
8
+ ░░▒▒░░░░░░░░░░░░░░░░░░░░
9
+ ░░▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░
10
+ ░░▒▒▒▒░░░░░░░░░░░░░░ ░░░░░░░░
11
+ ░▓▓▒▒▒▒░░░░░░░░░░░░ ░░░░░░░
12
+ ░▓▓▓▒▒▒▒░░░░░░░░░░░░ ░░░░░░░░
13
+ ░▓▓▓▒▒▒▒░░░░░░░░░░░░░░ ░░░░░░░░░░
14
+ ░▓▓▓▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░
15
+ ░▓▓▓▓▓▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░
16
+ ░▓▓▓▓▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░
17
+ ░░▓▓▓▓▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░
18
+ ░░▓▓▓▓▓▓▒▒▒▒▒▒▒▒░░░░░░░░░▒▒▒▒▒░░
19
+ ░░▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░
20
+ ░░▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░░
21
+
22
+ OURS TRULY,
23
+
24
+ */
25
+
26
+ /// Imagine. Mint. Enjoy.
27
+ /// @notice Interface for types used across the ZoraCreator1155 contract
28
+ /// @author @iainnash / @tbtstl
29
+ interface IZoraCreator1155TypesV1 {
30
+ /// @notice Used to store individual token data
31
+ struct TokenData {
32
+ string uri;
33
+ uint256 maxSupply;
34
+ uint256 totalMinted;
35
+ }
36
+
37
+ /// @notice Used to store contract-level configuration
38
+ struct ContractConfig {
39
+ address owner;
40
+ uint96 __gap1;
41
+ address payable fundsRecipient;
42
+ uint96 __gap2;
43
+ address transferHook;
44
+ uint96 __gap3;
45
+ }
46
+ }
@@ -0,0 +1,25 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.23;
3
+
4
+ interface IZoraTimedSaleStrategy {
5
+ function mint(address mintTo, uint256 quantity, address collection, uint256 tokenId, address mintReferral, string calldata comment) external payable;
6
+
7
+ /// @dev This is the SaleV1 style sale with a set end and start time and is used in both cases for storing key sale information
8
+ struct SaleStorage {
9
+ /// @notice The ERC20z address
10
+ address payable erc20zAddress;
11
+ /// @notice The sale start time
12
+ uint64 saleStart;
13
+ /// @notice The Uniswap pool address
14
+ address poolAddress;
15
+ /// @notice The sale end time
16
+ uint64 saleEnd;
17
+ /// @notice Boolean if the secondary market has been launched
18
+ bool secondaryActivated;
19
+ }
20
+
21
+ /// @notice Returns the sale config for a given token
22
+ /// @param collection The collection address
23
+ /// @param tokenId The ID of the token to get the sale config for
24
+ function sale(address collection, uint256 tokenId) external view returns (SaleStorage memory);
25
+ }
@@ -0,0 +1,43 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.23;
3
+
4
+ import {Enjoy} from "_imagine/Enjoy.sol";
5
+ import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
6
+
7
+ /*
8
+
9
+
10
+ ░░░░░░░░░░░░░░
11
+ ░░▒▒░░░░░░░░░░░░░░░░░░░░
12
+ ░░▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░
13
+ ░░▒▒▒▒░░░░░░░░░░░░░░ ░░░░░░░░
14
+ ░▓▓▒▒▒▒░░░░░░░░░░░░ ░░░░░░░
15
+ ░▓▓▓▒▒▒▒░░░░░░░░░░░░ ░░░░░░░░
16
+ ░▓▓▓▒▒▒▒░░░░░░░░░░░░░░ ░░░░░░░░░░
17
+ ░▓▓▓▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░
18
+ ░▓▓▓▓▓▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░
19
+ ░▓▓▓▓▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░
20
+ ░░▓▓▓▓▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░
21
+ ░░▓▓▓▓▓▓▒▒▒▒▒▒▒▒░░░░░░░░░▒▒▒▒▒░░
22
+ ░░▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░
23
+ ░░▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░░
24
+
25
+ OURS TRULY,
26
+
27
+
28
+ */
29
+
30
+ /// @title Zora Comments Proxy Contract
31
+ /// @notice This is the proxy contract for the CallerAndCommenter contract.
32
+ /// @dev Inherits from ERC1967Proxy to enable upgradeable functionality.
33
+ /// @notice Imagine. Mint. Enjoy.
34
+ /// @author @oveddan
35
+ contract CallerAndCommenter is Enjoy, ERC1967Proxy {
36
+ bytes32 internal immutable name;
37
+
38
+ constructor(address _logic) ERC1967Proxy(_logic, "") {
39
+ // added to create unique bytecode for this contract
40
+ // so that it can be properly verified as its own contract.
41
+ name = keccak256("CallerAndCommenter");
42
+ }
43
+ }
@@ -0,0 +1,376 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.23;
3
+
4
+ import {IComments} from "../interfaces/IComments.sol";
5
+ import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
6
+ import {EIP712UpgradeableWithChainId} from "./EIP712UpgradeableWithChainId.sol";
7
+ import {UnorderedNoncesUpgradeable} from "@zoralabs/shared-contracts/utils/UnorderedNoncesUpgradeable.sol";
8
+ import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
9
+ import {IZoraTimedSaleStrategy} from "../interfaces/IZoraTimedSaleStrategy.sol";
10
+ import {ICallerAndCommenter} from "../interfaces/ICallerAndCommenter.sol";
11
+ import {ContractVersionBase} from "../version/ContractVersionBase.sol";
12
+ import {IHasContractName} from "@zoralabs/shared-contracts/interfaces/IContractMetadata.sol";
13
+ import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
14
+ import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
15
+ import {ISecondarySwap} from "../interfaces/ISecondarySwap.sol";
16
+ import {IERC1155} from "@openzeppelin/contracts/interfaces/IERC1155.sol";
17
+
18
+ /// @title Calls contracts and allows a user to add a comment to be associated with the call.
19
+ /// @author oveddan
20
+ /// @dev Upgradeable contract. Is given permission to delegateComment on the Comments contract,
21
+ /// meaning it is trusted to indicate who a comment is from.
22
+ contract CallerAndCommenterImpl is
23
+ ICallerAndCommenter,
24
+ Ownable2StepUpgradeable,
25
+ EIP712UpgradeableWithChainId,
26
+ UnorderedNoncesUpgradeable,
27
+ ContractVersionBase,
28
+ UUPSUpgradeable,
29
+ IHasContractName
30
+ {
31
+ IComments public immutable comments;
32
+ IZoraTimedSaleStrategy public immutable zoraTimedSale;
33
+ ISecondarySwap public immutable secondarySwap;
34
+ uint256 public immutable sparkValue;
35
+
36
+ IComments.CommentIdentifier internal emptyCommentIdentifier;
37
+
38
+ string constant DOMAIN_NAME = "CallerAndCommenter";
39
+ string constant DOMAIN_VERSION = "1";
40
+
41
+ IComments.CommentIdentifier internal emptyReplyTo;
42
+
43
+ constructor(address _comments, address _zoraTimedSale, address _swapHelper, uint256 _sparksValue) {
44
+ comments = IComments(_comments);
45
+ zoraTimedSale = IZoraTimedSaleStrategy(_zoraTimedSale);
46
+ secondarySwap = ISecondarySwap(_swapHelper);
47
+ sparkValue = _sparksValue;
48
+ _disableInitializers();
49
+ }
50
+
51
+ /// Initializes the upgradeable contract
52
+ /// @param owner of the contract that can perform upgrades
53
+ function initialize(address owner) external initializer {
54
+ __Ownable_init(owner);
55
+ __UUPSUpgradeable_init();
56
+ __EIP712_init(DOMAIN_NAME, DOMAIN_VERSION);
57
+ }
58
+
59
+ /// @notice Mints tokens and adds a comment, without needing to pay a spark for the comment.
60
+ /// @dev The payable amount should be the total mint fee. No spark value should be sent.
61
+ /// @param quantity The number of tokens to mint
62
+ /// @param collection The address of the 1155 collection to mint from
63
+ /// @param tokenId The 1155 token Id to mint
64
+ /// @param mintReferral The address to receive mint referral rewards, if any
65
+ /// @param comment The comment to be added. If empty, no comment will be added.
66
+ /// @return The identifier of the newly created comment
67
+ function timedSaleMintAndComment(
68
+ address commenter,
69
+ uint256 quantity,
70
+ address collection,
71
+ uint256 tokenId,
72
+ address mintReferral,
73
+ string calldata comment
74
+ ) external payable returns (IComments.CommentIdentifier memory) {
75
+ if (commenter != msg.sender) {
76
+ revert CommenterMismatch(msg.sender, commenter);
77
+ }
78
+
79
+ return _timedSaleMintAndComment(commenter, quantity, collection, tokenId, mintReferral, comment);
80
+ }
81
+
82
+ function _timedSaleMintAndComment(
83
+ address commenter,
84
+ uint256 quantity,
85
+ address collection,
86
+ uint256 tokenId,
87
+ address mintReferral,
88
+ string calldata comment
89
+ ) internal returns (IComments.CommentIdentifier memory commentIdentifier) {
90
+ zoraTimedSale.mint{value: msg.value}(commenter, quantity, collection, tokenId, mintReferral, "");
91
+
92
+ if (bytes(comment).length > 0) {
93
+ bytes32 commentId;
94
+ (commentIdentifier, commentId) = comments.delegateComment(commenter, collection, tokenId, comment, emptyReplyTo, address(0), address(0));
95
+
96
+ emit MintedAndCommented(commentId, commentIdentifier, quantity, comment);
97
+ }
98
+ }
99
+
100
+ bytes32 constant PERMIT_TIMED_SALE_MINT_AND_COMMENT_DOMAIN =
101
+ keccak256(
102
+ "PermitTimedSaleMintAndComment(address commenter,uint256 quantity,address collection,uint256 tokenId,address mintReferral,string comment,uint256 deadline,bytes32 nonce,uint32 sourceChainId,uint32 destinationChainId)"
103
+ );
104
+
105
+ /// @notice Hashes the permit data for a timed sale mint and comment operation
106
+ /// @param permit The PermitTimedSaleMintAndComment struct containing the permit data
107
+ /// @return bytes32 The hash of the permit data for signing
108
+ function hashPermitTimedSaleMintAndComment(PermitTimedSaleMintAndComment memory permit) public view returns (bytes32) {
109
+ bytes32 structHash = keccak256(
110
+ abi.encode(
111
+ PERMIT_TIMED_SALE_MINT_AND_COMMENT_DOMAIN,
112
+ permit.commenter,
113
+ permit.quantity,
114
+ permit.collection,
115
+ permit.tokenId,
116
+ permit.mintReferral,
117
+ keccak256(bytes(permit.comment)),
118
+ permit.deadline,
119
+ permit.nonce,
120
+ permit.sourceChainId,
121
+ permit.destinationChainId
122
+ )
123
+ );
124
+
125
+ return _hashTypedDataV4(structHash, permit.sourceChainId);
126
+ }
127
+
128
+ function _validateSignature(bytes32 digest, bytes calldata signature, address signer) internal view {
129
+ if (!SignatureChecker.isValidSignatureNow(signer, digest, signature)) {
130
+ revert InvalidSignature();
131
+ }
132
+ }
133
+
134
+ function _validateAndUsePermit(
135
+ bytes32 digest,
136
+ bytes32 nonce,
137
+ bytes calldata signature,
138
+ address signer,
139
+ uint256 deadline,
140
+ uint32 destinationChainId
141
+ ) internal {
142
+ if (block.timestamp > deadline) {
143
+ revert ERC2612ExpiredSignature(deadline);
144
+ }
145
+
146
+ if (destinationChainId != uint32(block.chainid)) {
147
+ revert IncorrectDestinationChain(destinationChainId);
148
+ }
149
+
150
+ _useCheckedNonce(signer, nonce);
151
+ _validateSignature(digest, signature, signer);
152
+ }
153
+
154
+ /// @notice Mints tokens and adds a comment, without needing to pay a spark for the comment. Attributes the
155
+ /// comment to the signer of the message. Meant to be used for cross-chain commenting. where a permit
156
+ /// @dev The signer must match the commenter field in the permit.
157
+ /// @param permit The PermitTimedSaleMintAndComment struct containing the permit data
158
+ /// @param signature The signature of the permit
159
+ /// @return The identifier of the newly created comment
160
+ function permitTimedSaleMintAndComment(
161
+ PermitTimedSaleMintAndComment calldata permit,
162
+ bytes calldata signature
163
+ ) public payable returns (IComments.CommentIdentifier memory) {
164
+ bytes32 digest = hashPermitTimedSaleMintAndComment(permit);
165
+
166
+ _validateAndUsePermit(digest, permit.nonce, signature, permit.commenter, permit.deadline, permit.destinationChainId);
167
+
168
+ return _timedSaleMintAndComment(permit.commenter, permit.quantity, permit.collection, permit.tokenId, permit.mintReferral, permit.comment);
169
+ }
170
+
171
+ /// @notice Buys Zora 1155 tokens on secondary market and adds a comment, without needing to pay a spark for the comment.
172
+ /// @param commenter The address of the commenter. Must match the msg.sender. Commenter will be the recipient of the bought tokens.
173
+ /// @param quantity The number of tokens to buy
174
+ /// @param collection The address of the 1155 collection
175
+ /// @param tokenId The 1155 token Id to buy
176
+ /// @param excessRefundRecipient The address to receive any excess ETH refund
177
+ /// @param maxEthToSpend The maximum amount of ETH to spend on the purchase
178
+ /// @param sqrtPriceLimitX96 The sqrt price limit for the swap
179
+ /// @param comment The comment to be added
180
+ /// @return The identifier of the newly created comment
181
+ /// @dev This function can only be called by the commenter themselves
182
+ function buyOnSecondaryAndComment(
183
+ address commenter,
184
+ uint256 quantity,
185
+ address collection,
186
+ uint256 tokenId,
187
+ address payable excessRefundRecipient,
188
+ uint256 maxEthToSpend,
189
+ uint160 sqrtPriceLimitX96,
190
+ string calldata comment
191
+ ) external payable returns (IComments.CommentIdentifier memory) {
192
+ if (commenter != msg.sender) {
193
+ revert CommenterMismatch(msg.sender, commenter);
194
+ }
195
+
196
+ return _buyOnSecondaryAndComment(commenter, quantity, collection, tokenId, excessRefundRecipient, maxEthToSpend, sqrtPriceLimitX96, comment);
197
+ }
198
+
199
+ function _buyOnSecondaryAndComment(
200
+ address commenter,
201
+ uint256 quantity,
202
+ address collection,
203
+ uint256 tokenId,
204
+ address payable excessRefundRecipient,
205
+ uint256 maxEthToSpend,
206
+ uint160 sqrtPriceLimitX96,
207
+ string calldata comment
208
+ ) internal returns (IComments.CommentIdentifier memory commentIdentifier) {
209
+ address erc20zAddress = zoraTimedSale.sale(collection, tokenId).erc20zAddress;
210
+ if (erc20zAddress == address(0)) {
211
+ revert SaleNotSet(collection, tokenId);
212
+ }
213
+
214
+ secondarySwap.buy1155{value: msg.value}({
215
+ erc20zAddress: erc20zAddress,
216
+ num1155ToBuy: quantity,
217
+ recipient: payable(commenter),
218
+ excessRefundRecipient: excessRefundRecipient,
219
+ maxEthToSpend: maxEthToSpend,
220
+ sqrtPriceLimitX96: sqrtPriceLimitX96
221
+ });
222
+
223
+ if (bytes(comment).length > 0) {
224
+ bytes32 commentId;
225
+ (commentIdentifier, commentId) = comments.delegateComment(commenter, collection, tokenId, comment, emptyReplyTo, address(0), address(0));
226
+
227
+ emit SwappedOnSecondaryAndCommented(commentId, commentIdentifier, quantity, comment, SwapDirection.BUY);
228
+ }
229
+
230
+ return commentIdentifier;
231
+ }
232
+
233
+ bytes32 constant PERMIT_BUY_ON_SECONDARY_AND_COMMENT_DOMAIN =
234
+ keccak256(
235
+ "PermitBuyOnSecondaryAndComment(address commenter,uint256 quantity,address collection,uint256 tokenId,uint256 maxEthToSpend,uint160 sqrtPriceLimitX96,string comment,uint256 deadline,bytes32 nonce,uint32 sourceChainId,uint32 destinationChainId)"
236
+ );
237
+
238
+ function _hashPermitBuyOnSecondaryAndComment(PermitBuyOnSecondaryAndComment memory permit) internal pure returns (bytes memory) {
239
+ return
240
+ abi.encode(
241
+ PERMIT_BUY_ON_SECONDARY_AND_COMMENT_DOMAIN,
242
+ permit.commenter,
243
+ permit.quantity,
244
+ permit.collection,
245
+ permit.tokenId,
246
+ permit.maxEthToSpend,
247
+ permit.sqrtPriceLimitX96,
248
+ keccak256(bytes(permit.comment)),
249
+ permit.deadline,
250
+ permit.nonce,
251
+ permit.sourceChainId,
252
+ permit.destinationChainId
253
+ );
254
+ }
255
+
256
+ /// @notice Hashes the permit data for a buy on secondary and comment operation
257
+ /// @param permit The PermitBuyOnSecondaryAndComment struct containing the permit data
258
+ /// @return bytes32 The hash of the permit data for signing
259
+ function hashPermitBuyOnSecondaryAndComment(PermitBuyOnSecondaryAndComment memory permit) public view returns (bytes32) {
260
+ return _hashTypedDataV4(keccak256(_hashPermitBuyOnSecondaryAndComment(permit)), permit.sourceChainId);
261
+ }
262
+
263
+ /// @notice Buys tokens on secondary market and adds a comment, without needing to pay a spark for the comment. Attributes the
264
+ /// comment to the signer of the message. Meant to be used for cross-chain commenting where a permit is used.
265
+ /// @dev The signer must match the commenter field in the permit.
266
+ /// @param permit The PermitBuyOnSecondaryAndComment struct containing the permit data
267
+ /// @param signature The signature of the permit
268
+ /// @return The identifier of the newly created comment
269
+ function permitBuyOnSecondaryAndComment(
270
+ PermitBuyOnSecondaryAndComment calldata permit,
271
+ bytes calldata signature
272
+ ) public payable returns (IComments.CommentIdentifier memory) {
273
+ bytes32 digest = hashPermitBuyOnSecondaryAndComment(permit);
274
+
275
+ _validateAndUsePermit(digest, permit.nonce, signature, permit.commenter, permit.deadline, permit.destinationChainId);
276
+
277
+ return
278
+ _buyOnSecondaryAndComment(
279
+ permit.commenter,
280
+ permit.quantity,
281
+ permit.collection,
282
+ permit.tokenId,
283
+ payable(permit.commenter),
284
+ permit.maxEthToSpend,
285
+ permit.sqrtPriceLimitX96,
286
+ permit.comment
287
+ );
288
+ }
289
+
290
+ /// @notice Sells Zora 1155 tokens on secondary market and adds a comment.
291
+ /// @dev Must sent ETH value of one spark for the comment. Commenter must have approved this contract to transfer the tokens
292
+ /// on the 1155 contract.
293
+ /// @param commenter The address of the commenter. Must match the msg.sender. Commenter will be the seller of the tokens.
294
+ /// @param quantity The number of tokens to sell
295
+ /// @param collection The address of the 1155 collection
296
+ /// @param tokenId The 1155 token Id to sell
297
+ /// @param recipient The address to receive the ETH proceeds
298
+ /// @param minEthToAcquire The minimum amount of ETH to receive from the sale
299
+ /// @param sqrtPriceLimitX96 The sqrt price limit for the swap
300
+ /// @param comment The comment to be added
301
+ /// @return commentIdentifier The identifier of the newly created comment
302
+ /// @dev This function can only be called by the commenter themselves
303
+ function sellOnSecondaryAndComment(
304
+ address commenter,
305
+ uint256 quantity,
306
+ address collection,
307
+ uint256 tokenId,
308
+ address payable recipient,
309
+ uint256 minEthToAcquire,
310
+ uint160 sqrtPriceLimitX96,
311
+ string calldata comment
312
+ ) external payable returns (IComments.CommentIdentifier memory commentIdentifier) {
313
+ if (commenter != msg.sender) {
314
+ revert CommenterMismatch(msg.sender, commenter);
315
+ }
316
+
317
+ if (bytes(comment).length == 0) {
318
+ // if we are not sending a comment, we should not send any ETH
319
+ if (msg.value != 0) {
320
+ revert WrongValueSent(0, msg.value);
321
+ }
322
+ } else {
323
+ // if we are sending a comment, we should be required to send one spark
324
+ if (msg.value != sparkValue) {
325
+ revert WrongValueSent(sparkValue, msg.value);
326
+ }
327
+
328
+ bytes32 commentId;
329
+ // submit the comment, attaching the spark value if it is sent
330
+ (commentIdentifier, commentId) = comments.delegateComment{value: msg.value}(
331
+ commenter,
332
+ collection,
333
+ tokenId,
334
+ comment,
335
+ emptyReplyTo,
336
+ address(0),
337
+ address(0)
338
+ );
339
+
340
+ emit SwappedOnSecondaryAndCommented(commentId, commentIdentifier, quantity, comment, SwapDirection.SELL);
341
+ }
342
+
343
+ // wrapped around brackets to prevent stack too deep error
344
+ {
345
+ // transfer the tokens to the secondary swap
346
+ IERC1155(collection).safeTransferFrom(
347
+ // transferring from the commenter to the secondary swap contract.
348
+ // commenter must have approved this contract to transfer tokens.
349
+ address(commenter),
350
+ address(secondarySwap),
351
+ tokenId,
352
+ quantity,
353
+ abi.encode(recipient, minEthToAcquire, sqrtPriceLimitX96)
354
+ );
355
+ }
356
+
357
+ return commentIdentifier;
358
+ }
359
+
360
+ /// @notice Returns the name of the contract
361
+ /// @return The name of the contract
362
+ function contractName() public pure returns (string memory) {
363
+ return "Caller and Commenter";
364
+ }
365
+
366
+ function _authorizeUpgrade(address newImplementation) internal override onlyOwner {
367
+ // check that new implementation's contract name matches the current contract name
368
+ if (!Strings.equal(IHasContractName(newImplementation).contractName(), this.contractName())) {
369
+ revert UpgradeToMismatchedContractName(this.contractName(), IHasContractName(newImplementation).contractName());
370
+ }
371
+ }
372
+
373
+ function _equals(string memory a, string memory b) internal pure returns (bool) {
374
+ return (keccak256(bytes(a)) == keccak256(bytes(b)));
375
+ }
376
+ }
@@ -1,36 +1,25 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.20;
3
3
 
4
- import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
4
+ import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol";
5
+ import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
5
6
 
6
- abstract contract EIP712UpgradeableWithChainId is Initializable {
7
+ /// Extension of EIP712Upgradeable that allows for messages to be signed on other chains.
8
+ abstract contract EIP712UpgradeableWithChainId is EIP712Upgradeable {
7
9
  bytes32 private constant TYPE_HASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
8
10
 
9
- struct EIP712Storage {
10
- string _name;
11
- string _version;
12
- }
13
-
14
- bytes32 private constant EIP712StorageLocation = 0xa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100;
15
-
16
- function _getEIP712Storage() private pure returns (EIP712Storage storage $) {
17
- assembly {
18
- $.slot := EIP712StorageLocation
19
- }
20
- }
21
-
22
- function __EIP712_init(string memory name, string memory version) internal onlyInitializing {
23
- EIP712Storage storage $ = _getEIP712Storage();
24
- $._name = name;
25
- $._version = version;
11
+ /**
12
+ * @dev Returns the domain separator for the current chain.
13
+ */
14
+ function _domainSeparatorV4(uint256 chainId) internal view returns (bytes32) {
15
+ return _buildDomainSeparator(chainId);
26
16
  }
27
17
 
28
- function _domainSeparatorV4(uint256 chainId) internal view returns (bytes32) {
29
- EIP712Storage storage $ = _getEIP712Storage();
30
- return keccak256(abi.encode(TYPE_HASH, keccak256(bytes($._name)), keccak256(bytes($._version)), chainId, address(this)));
18
+ function _buildDomainSeparator(uint256 chainId) private view returns (bytes32) {
19
+ return keccak256(abi.encode(TYPE_HASH, _EIP712NameHash(), _EIP712VersionHash(), chainId, address(this)));
31
20
  }
32
21
 
33
22
  function _hashTypedDataV4(bytes32 structHash, uint256 chainId) internal view returns (bytes32) {
34
- return keccak256(abi.encodePacked("\x19\x01", _domainSeparatorV4(chainId), structHash));
23
+ return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(chainId), structHash);
35
24
  }
36
25
  }
@@ -9,6 +9,6 @@ import {IVersionedContract} from "@zoralabs/shared-contracts/interfaces/IVersion
9
9
  contract ContractVersionBase is IVersionedContract {
10
10
  /// @notice The version of the contract
11
11
  function contractVersion() external pure override returns (string memory) {
12
- return "0.0.1";
12
+ return "0.0.2";
13
13
  }
14
14
  }
@@ -0,0 +1,77 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.20;
3
+
4
+ import {Test} from "forge-std/Test.sol";
5
+
6
+ import {CallerAndCommenterImpl} from "../src/utils/CallerAndCommenterImpl.sol";
7
+ import {CommentsImpl} from "../src/CommentsImpl.sol";
8
+ import {Comments} from "../src/proxy/Comments.sol";
9
+ import {IComments} from "../src/interfaces/IComments.sol";
10
+ import {Mock1155} from "./mocks/Mock1155.sol";
11
+ import {MockZoraTimedSale} from "./mocks/MockZoraTimedSale.sol";
12
+ import {MockSecondarySwap} from "./mocks/MockSecondarySwap.sol";
13
+ import {ICallerAndCommenter} from "../src/interfaces/ICallerAndCommenter.sol";
14
+ import {CallerAndCommenter} from "../src/proxy/CallerAndCommenter.sol";
15
+
16
+ contract CallerAndCommenterTestBase is Test {
17
+ uint256 constant SPARKS_VALUE = 0.000001 ether;
18
+
19
+ address zoraRecipient = makeAddr("zoraRecipient");
20
+ address commentsAdmin = makeAddr("commentsAdmin");
21
+ address commenter;
22
+ uint256 commenterPrivateKey;
23
+ address backfiller = makeAddr("backfiller");
24
+ address tokenAdmin = makeAddr("tokenAdmin");
25
+ address protocolRewards = 0x7777777F279eba3d3Ad8F4E708545291A6fDBA8B;
26
+
27
+ IComments.CommentIdentifier emptyCommentIdentifier;
28
+
29
+ MockSecondarySwap mockSecondarySwap;
30
+
31
+ IComments comments;
32
+
33
+ Mock1155 mock1155;
34
+
35
+ uint256 tokenId1 = 1;
36
+
37
+ MockZoraTimedSale mockMinter;
38
+ ICallerAndCommenter callerAndCommenter;
39
+
40
+ function setUp() public {
41
+ vm.createSelectFork("zora_sepolia", 16028863);
42
+
43
+ (commenter, commenterPrivateKey) = makeAddrAndKey("commenter");
44
+
45
+ mock1155 = new Mock1155();
46
+ mock1155.createToken(tokenId1, tokenAdmin);
47
+
48
+ CommentsImpl commentsImpl = new CommentsImpl(SPARKS_VALUE, protocolRewards, zoraRecipient);
49
+
50
+ mockMinter = new MockZoraTimedSale();
51
+ mockSecondarySwap = new MockSecondarySwap(mockMinter);
52
+
53
+ comments = IComments(payable(address(new Comments(address(commentsImpl)))));
54
+
55
+ CallerAndCommenterImpl callerAndCommenterImpl = new CallerAndCommenterImpl(
56
+ address(comments),
57
+ address(mockMinter),
58
+ address(mockSecondarySwap),
59
+ SPARKS_VALUE
60
+ );
61
+ callerAndCommenter = ICallerAndCommenter(payable(address(new CallerAndCommenter(address(callerAndCommenterImpl)))));
62
+
63
+ address[] memory delegateCommenters = new address[](1);
64
+ delegateCommenters[0] = address(callerAndCommenter);
65
+
66
+ comments.initialize(commentsAdmin, backfiller, delegateCommenters);
67
+ callerAndCommenter.initialize(commentsAdmin);
68
+ }
69
+
70
+ function _expectedCommentIdentifier(
71
+ address _commenter,
72
+ address contractAddress,
73
+ uint256 tokenId
74
+ ) internal view returns (IComments.CommentIdentifier memory) {
75
+ return IComments.CommentIdentifier({commenter: _commenter, contractAddress: contractAddress, tokenId: tokenId, nonce: comments.nextNonce()});
76
+ }
77
+ }