@zoralabs/comments-contracts 0.0.2 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +71 -65
- package/CHANGELOG.md +15 -0
- package/abis/CommentsImpl.json +0 -5
- package/abis/CommentsPermitTest.json +2 -2
- package/abis/CommentsTest.json +30 -2
- package/abis/Comments_mintAndCommentTest.json +8 -1
- package/abis/Comments_smartWallet.json +36 -1
- package/abis/ICoinComments.json +53 -0
- package/abis/IComments.json +0 -5
- package/abis/IProtocolRewards.json +18 -0
- package/abis/MockCoin.json +395 -0
- package/abis/Vm.json +1482 -111
- package/abis/VmSafe.json +856 -32
- package/addresses/1.json +2 -2
- package/addresses/10.json +2 -2
- package/addresses/42161.json +2 -2
- package/addresses/7777777.json +2 -2
- package/addresses/81457.json +2 -2
- package/addresses/8453.json +2 -2
- package/addresses/84532.json +2 -2
- package/addresses/999999999.json +2 -2
- package/dist/index.cjs +133 -134
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +130 -131
- package/dist/index.js.map +1 -1
- package/dist/wagmiGenerated.d.ts +65 -69
- package/dist/wagmiGenerated.d.ts.map +1 -1
- package/package/wagmiGenerated.ts +65 -66
- package/package.json +9 -8
- package/script/storage-check.sh +1 -1
- package/src/CommentsImpl.sol +41 -26
- package/src/interfaces/ICoinComments.sol +8 -0
- package/src/interfaces/IComments.sol +0 -3
- package/src/version/ContractVersionBase.sol +1 -1
- package/test/Comments.t.sol +68 -4
- package/test/CommentsTestBase.sol +7 -0
- package/test/Comments_delegateComment.t.sol +19 -2
- package/test/Comments_permit.t.sol +3 -3
- package/test/Comments_smartWallet.t.sol +97 -2
- package/test/mocks/MockCoin.sol +42 -0
- package/script/bundle-abis.ts +0 -109
- package/script/update-contract-version.ts +0 -63
package/src/CommentsImpl.sol
CHANGED
|
@@ -4,11 +4,13 @@ pragma solidity ^0.8.23;
|
|
|
4
4
|
import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
|
|
5
5
|
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
|
|
6
6
|
import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
|
|
7
|
+
import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol";
|
|
7
8
|
import {IHasContractName} from "@zoralabs/shared-contracts/interfaces/IContractMetadata.sol";
|
|
8
9
|
import {ContractVersionBase} from "./version/ContractVersionBase.sol";
|
|
9
10
|
import {IZoraCreator1155} from "./interfaces/IZoraCreator1155.sol";
|
|
10
11
|
import {IComments} from "./interfaces/IComments.sol";
|
|
11
|
-
import {
|
|
12
|
+
import {ICoinComments} from "./interfaces/ICoinComments.sol";
|
|
13
|
+
import {IProtocolRewards} from "@zoralabs/shared-contracts/interfaces/IProtocolRewards.sol";
|
|
12
14
|
import {UnorderedNoncesUpgradeable} from "@zoralabs/shared-contracts/utils/UnorderedNoncesUpgradeable.sol";
|
|
13
15
|
import {EIP712UpgradeableWithChainId} from "./utils/EIP712UpgradeableWithChainId.sol";
|
|
14
16
|
import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
|
|
@@ -140,7 +142,7 @@ contract CommentsImpl is
|
|
|
140
142
|
}
|
|
141
143
|
}
|
|
142
144
|
|
|
143
|
-
/// @notice Creates a new comment. Equivalant sparks value in eth must be sent with the transaction.
|
|
145
|
+
/// @notice Creates a new comment. Equivalant sparks value in eth must be sent with the transaction.
|
|
144
146
|
/// If not the owner, must send 1 spark.
|
|
145
147
|
/// @param contractAddress The address of the contract
|
|
146
148
|
/// @param tokenId The token ID
|
|
@@ -307,13 +309,7 @@ contract CommentsImpl is
|
|
|
307
309
|
) {
|
|
308
310
|
return;
|
|
309
311
|
}
|
|
310
|
-
// if they aren't
|
|
311
|
-
if (
|
|
312
|
-
!_accountOrSmartWalletIsTokenHolder(commentIdentifier.contractAddress, commentIdentifier.tokenId, commentIdentifier.commenter, commenterSmartWallet)
|
|
313
|
-
) {
|
|
314
|
-
revert NotTokenHolderOrAdmin();
|
|
315
|
-
}
|
|
316
|
-
|
|
312
|
+
// if they aren't an admin, they must include at least 1 spark
|
|
317
313
|
if (mustSendAtLeastOneSpark && sparksQuantity == 0) {
|
|
318
314
|
revert MustSendAtLeastOneSpark();
|
|
319
315
|
}
|
|
@@ -365,31 +361,33 @@ contract CommentsImpl is
|
|
|
365
361
|
}
|
|
366
362
|
|
|
367
363
|
function _accountOrSmartWalletIsTokenAdmin(address contractAddress, uint256 tokenId, address user, address smartWallet) internal view returns (bool) {
|
|
368
|
-
|
|
369
|
-
return true;
|
|
370
|
-
}
|
|
371
|
-
if (smartWallet != address(0)) {
|
|
372
|
-
return _isTokenAdmin(contractAddress, tokenId, smartWallet);
|
|
373
|
-
}
|
|
374
|
-
return false;
|
|
375
|
-
}
|
|
364
|
+
bool isCoin = _isCoinComment(contractAddress, tokenId);
|
|
376
365
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
if (smartWallet != address(0)) {
|
|
382
|
-
return _isTokenHolder(contractAddress, tokenId, smartWallet);
|
|
366
|
+
if (isCoin) {
|
|
367
|
+
return ICoinComments(contractAddress).isOwner(user) || (smartWallet != address(0) && ICoinComments(contractAddress).isOwner(smartWallet));
|
|
368
|
+
} else {
|
|
369
|
+
return _isTokenAdmin(contractAddress, tokenId, user) || (smartWallet != address(0) && _isTokenAdmin(contractAddress, tokenId, smartWallet));
|
|
383
370
|
}
|
|
384
|
-
return false;
|
|
385
371
|
}
|
|
386
372
|
|
|
387
373
|
function _isTokenAdmin(address contractAddress, uint256 tokenId, address user) internal view returns (bool) {
|
|
388
|
-
|
|
374
|
+
try IZoraCreator1155(contractAddress).isAdminOrRole(user, tokenId, PERMISSION_BIT_ADMIN) returns (bool isAdmin) {
|
|
375
|
+
return isAdmin;
|
|
376
|
+
} catch {
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
389
379
|
}
|
|
390
380
|
|
|
391
381
|
function _isTokenHolder(address contractAddress, uint256 tokenId, address user) internal view returns (bool) {
|
|
392
|
-
|
|
382
|
+
try IERC1155(contractAddress).balanceOf(user, tokenId) returns (uint256 balance) {
|
|
383
|
+
return balance > 0;
|
|
384
|
+
} catch {
|
|
385
|
+
return false;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function _isCoinComment(address contractAddress, uint256 tokenId) internal view returns (bool) {
|
|
390
|
+
return tokenId == 0 && IERC165(contractAddress).supportsInterface(type(ICoinComments).interfaceId);
|
|
393
391
|
}
|
|
394
392
|
|
|
395
393
|
function _getCommentSparksRecipient(CommentIdentifier memory commentIdentifier, CommentIdentifier memory replyTo) internal view returns (address) {
|
|
@@ -629,6 +627,18 @@ contract CommentsImpl is
|
|
|
629
627
|
}
|
|
630
628
|
}
|
|
631
629
|
|
|
630
|
+
function _getCoinPayoutRecipient(address contractAddress, uint256 tokenId) internal view returns (address) {
|
|
631
|
+
if (_isCoinComment(contractAddress, tokenId)) {
|
|
632
|
+
try ICoinComments(contractAddress).payoutRecipient() returns (address payoutRecipient) {
|
|
633
|
+
return payoutRecipient;
|
|
634
|
+
} catch {
|
|
635
|
+
return address(0);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
return address(0);
|
|
640
|
+
}
|
|
641
|
+
|
|
632
642
|
function _getFundsRecipient(address contractAddress) internal view returns (address) {
|
|
633
643
|
try IZoraCreator1155(contractAddress).config() returns (address, uint96, address payable fundsRecipient, uint96, address, uint96) {
|
|
634
644
|
if (fundsRecipient != address(0)) {
|
|
@@ -654,6 +664,11 @@ contract CommentsImpl is
|
|
|
654
664
|
}
|
|
655
665
|
|
|
656
666
|
function _getCreatorRewardRecipient(CommentIdentifier memory commentIdentifier) internal view returns (address) {
|
|
667
|
+
address payoutRecipient = _getCoinPayoutRecipient(commentIdentifier.contractAddress, commentIdentifier.tokenId);
|
|
668
|
+
if (payoutRecipient != address(0)) {
|
|
669
|
+
return payoutRecipient;
|
|
670
|
+
}
|
|
671
|
+
|
|
657
672
|
address creatorRecipient = _tryGetCreatorRewardRecipient(commentIdentifier);
|
|
658
673
|
if (creatorRecipient != address(0)) {
|
|
659
674
|
return creatorRecipient;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.0;
|
|
3
|
+
|
|
4
|
+
interface ICoinComments {
|
|
5
|
+
function isOwner(address) external view returns (bool);
|
|
6
|
+
function payoutRecipient() external view returns (address);
|
|
7
|
+
function balanceOf(address) external view returns (uint256);
|
|
8
|
+
}
|
|
@@ -133,9 +133,6 @@ interface IComments {
|
|
|
133
133
|
/// @param actual The actual address that attempted to comment
|
|
134
134
|
error CommenterMismatch(address expected, address actual);
|
|
135
135
|
|
|
136
|
-
/// @notice Occurs when a non-token holder or non-admin attempts an action restricted to token holders or admins
|
|
137
|
-
error NotTokenHolderOrAdmin();
|
|
138
|
-
|
|
139
136
|
/// @notice Occurs when attempting to spark a comment without sending at least one spark
|
|
140
137
|
error MustSendAtLeastOneSpark();
|
|
141
138
|
|
|
@@ -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
|
|
12
|
+
return "0.1.0";
|
|
13
13
|
}
|
|
14
14
|
}
|
package/test/Comments.t.sol
CHANGED
|
@@ -470,14 +470,13 @@ contract CommentsTest is CommentsTestBase {
|
|
|
470
470
|
postComment(commenter, address(mock1155), tokenId1, "", emptyCommentIdentifier);
|
|
471
471
|
}
|
|
472
472
|
|
|
473
|
-
function
|
|
473
|
+
function testNonHolderCanCommentWithSpark() public {
|
|
474
474
|
address nonHolder = makeAddr("nonHolder");
|
|
475
475
|
|
|
476
|
-
// We don't set up the commenter with a token,
|
|
476
|
+
// We don't set up the commenter with a token, but they should be able to comment with a spark
|
|
477
477
|
vm.deal(nonHolder, SPARKS_VALUE);
|
|
478
478
|
|
|
479
|
-
|
|
480
|
-
postComment(nonHolder, address(mock1155), tokenId1, "Attempting to comment without holding token", emptyCommentIdentifier);
|
|
479
|
+
postComment(nonHolder, address(mock1155), tokenId1, "Commenting without holding token", emptyCommentIdentifier);
|
|
481
480
|
}
|
|
482
481
|
|
|
483
482
|
function testCommentRevertsWhenSendTooMuchValue() public {
|
|
@@ -616,4 +615,69 @@ contract CommentsTest is CommentsTestBase {
|
|
|
616
615
|
vm.expectRevert(abi.encodeWithSignature("AccessControlUnauthorizedAccount(address,bytes32)", notAdmin, no_role));
|
|
617
616
|
comments.grantRole(BACKFILLER_ROLE, newBackfiller);
|
|
618
617
|
}
|
|
618
|
+
|
|
619
|
+
function testCommentAsCoinAdmin() public {
|
|
620
|
+
IComments.CommentIdentifier memory expectedCommentId = _expectedCommentIdentifier(address(mockCoin), tokenId0, tokenAdmin);
|
|
621
|
+
|
|
622
|
+
vm.prank(tokenAdmin);
|
|
623
|
+
IComments.CommentIdentifier memory commentId = comments.comment(
|
|
624
|
+
tokenAdmin,
|
|
625
|
+
address(mockCoin),
|
|
626
|
+
tokenId0,
|
|
627
|
+
"test comment",
|
|
628
|
+
emptyCommentIdentifier,
|
|
629
|
+
address(0),
|
|
630
|
+
address(0)
|
|
631
|
+
);
|
|
632
|
+
|
|
633
|
+
(bytes32 commentIdHash, bool exists) = comments.hashAndCheckCommentExists(commentId);
|
|
634
|
+
|
|
635
|
+
assertEq(exists, true);
|
|
636
|
+
assertEq(commentIdHash, comments.hashCommentIdentifier(expectedCommentId));
|
|
637
|
+
assertEq(comments.commentSparksQuantity(commentId), 0);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function testCommentAsCoinHolder() public {
|
|
641
|
+
address commenter = makeAddr("commenter");
|
|
642
|
+
|
|
643
|
+
mockCoin.mint(commenter, 1e18);
|
|
644
|
+
vm.deal(commenter, SPARKS_VALUE);
|
|
645
|
+
|
|
646
|
+
IComments.CommentIdentifier memory expectedCommentId = _expectedCommentIdentifier(address(mockCoin), tokenId0, commenter);
|
|
647
|
+
|
|
648
|
+
vm.prank(commenter);
|
|
649
|
+
IComments.CommentIdentifier memory commentId = comments.comment{value: SPARKS_VALUE}(
|
|
650
|
+
commenter,
|
|
651
|
+
address(mockCoin),
|
|
652
|
+
tokenId0,
|
|
653
|
+
"test comment",
|
|
654
|
+
emptyCommentIdentifier,
|
|
655
|
+
address(0),
|
|
656
|
+
address(0)
|
|
657
|
+
);
|
|
658
|
+
|
|
659
|
+
(bytes32 commentIdHash, bool exists) = comments.hashAndCheckCommentExists(commentId);
|
|
660
|
+
assertEq(exists, true);
|
|
661
|
+
assertEq(commentIdHash, comments.hashCommentIdentifier(expectedCommentId));
|
|
662
|
+
|
|
663
|
+
uint256 zoraReward = (SPARKS_VALUE * (ZORA_REWARD_PCT + REFERRER_REWARD_PCT)) / BPS_TO_PERCENT_2_DECIMAL_PERCISION;
|
|
664
|
+
assertEq(protocolRewards.balanceOf(tokenAdmin), SPARKS_VALUE - zoraReward);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function testRevertCommentAsCoinHolderWithoutSparks() public {
|
|
668
|
+
address commenter = makeAddr("commenter");
|
|
669
|
+
mockCoin.mint(commenter, 1e18);
|
|
670
|
+
|
|
671
|
+
vm.expectRevert(abi.encodeWithSignature("MustSendAtLeastOneSpark()"));
|
|
672
|
+
vm.prank(commenter);
|
|
673
|
+
comments.comment(commenter, address(mockCoin), tokenId0, "test comment", emptyCommentIdentifier, address(0), address(0));
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function testNonCoinHolderCanCommentWithSpark() public {
|
|
677
|
+
address nonHolder = makeAddr("nonHolder");
|
|
678
|
+
vm.deal(nonHolder, SPARKS_VALUE);
|
|
679
|
+
|
|
680
|
+
vm.prank(nonHolder);
|
|
681
|
+
comments.comment{value: SPARKS_VALUE}(nonHolder, address(mockCoin), tokenId0, "test comment", emptyCommentIdentifier, address(0), address(0));
|
|
682
|
+
}
|
|
619
683
|
}
|
|
@@ -7,12 +7,14 @@ import {CommentsImpl} from "../src/CommentsImpl.sol";
|
|
|
7
7
|
import {Comments} from "../src/proxy/Comments.sol";
|
|
8
8
|
import {IComments} from "../src/interfaces/IComments.sol";
|
|
9
9
|
import {Mock1155} from "./mocks/Mock1155.sol";
|
|
10
|
+
import {MockCoin} from "./mocks/MockCoin.sol";
|
|
10
11
|
import {ProtocolRewards} from "./mocks/ProtocolRewards.sol";
|
|
11
12
|
|
|
12
13
|
contract CommentsTestBase is Test {
|
|
13
14
|
CommentsImpl internal comments;
|
|
14
15
|
CommentsImpl internal commentsImpl;
|
|
15
16
|
Mock1155 internal mock1155;
|
|
17
|
+
MockCoin internal mockCoin;
|
|
16
18
|
|
|
17
19
|
uint256 internal constant SPARKS_VALUE = 0.000001 ether;
|
|
18
20
|
|
|
@@ -30,6 +32,7 @@ contract CommentsTestBase is Test {
|
|
|
30
32
|
address internal sparker;
|
|
31
33
|
uint256 internal sparkerPrivateKey;
|
|
32
34
|
|
|
35
|
+
uint256 internal tokenId0 = 0;
|
|
33
36
|
uint256 internal tokenId1 = 1;
|
|
34
37
|
uint256 internal tokenId2 = 2;
|
|
35
38
|
|
|
@@ -52,6 +55,10 @@ contract CommentsTestBase is Test {
|
|
|
52
55
|
|
|
53
56
|
(collectorWithToken, collectorWithTokenPrivateKey) = makeAddrAndKey("collectorWithToken");
|
|
54
57
|
(sparker, sparkerPrivateKey) = makeAddrAndKey("sparker");
|
|
58
|
+
|
|
59
|
+
address[] memory owners = new address[](1);
|
|
60
|
+
owners[0] = tokenAdmin;
|
|
61
|
+
mockCoin = new MockCoin(tokenAdmin, owners);
|
|
55
62
|
}
|
|
56
63
|
|
|
57
64
|
function _expectedCommentIdentifier(
|
|
@@ -120,10 +120,27 @@ contract Comments_mintAndCommentTest is Test {
|
|
|
120
120
|
});
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
function
|
|
123
|
+
function test_delegateComment_nonOwnerCanCommentWithSpark() public {
|
|
124
124
|
address notOwner = makeAddr("notOwner");
|
|
125
125
|
vm.prank(notOwner);
|
|
126
|
-
|
|
126
|
+
|
|
127
127
|
mockDelegateCommenter.forwardComment(address(mock1155), tokenId1, "test");
|
|
128
128
|
}
|
|
129
|
+
|
|
130
|
+
function test_delegateComment_canCommentWithZeroSparks() public {
|
|
131
|
+
// Delegate commenter should be able to comment with 0 spark value
|
|
132
|
+
vm.prank(commenter);
|
|
133
|
+
mockDelegateCommenter.forwardComment(address(mock1155), tokenId1, "test comment with 0 sparks");
|
|
134
|
+
|
|
135
|
+
// Verify the comment was created successfully
|
|
136
|
+
IComments.CommentIdentifier memory expectedCommentIdentifier = IComments.CommentIdentifier({
|
|
137
|
+
commenter: commenter,
|
|
138
|
+
contractAddress: address(mock1155),
|
|
139
|
+
tokenId: tokenId1,
|
|
140
|
+
nonce: bytes32(0)
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
(, bool exists) = comments.hashAndCheckCommentExists(expectedCommentIdentifier);
|
|
144
|
+
assertTrue(exists, "Comment should exist");
|
|
145
|
+
}
|
|
129
146
|
}
|
|
@@ -7,7 +7,7 @@ import {CommentsImpl} from "../src/CommentsImpl.sol";
|
|
|
7
7
|
import {Comments} from "../src/proxy/Comments.sol";
|
|
8
8
|
import {IComments} from "../src/interfaces/IComments.sol";
|
|
9
9
|
import {Mock1155} from "./mocks/Mock1155.sol";
|
|
10
|
-
import {IProtocolRewards} from "@zoralabs/
|
|
10
|
+
import {IProtocolRewards} from "@zoralabs/shared-contracts/interfaces/IProtocolRewards.sol";
|
|
11
11
|
import {ProtocolRewards} from "./mocks/ProtocolRewards.sol";
|
|
12
12
|
import {CommentsTestBase} from "./CommentsTestBase.sol";
|
|
13
13
|
import {UnorderedNoncesUpgradeable} from "@zoralabs/shared-contracts/utils/UnorderedNoncesUpgradeable.sol";
|
|
@@ -124,13 +124,13 @@ contract CommentsPermitTest is CommentsTestBase {
|
|
|
124
124
|
_assertCommentExists(expectedCommentIdentifier);
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
function
|
|
127
|
+
function testPermitComment_Non1155HolderCanCommentWithSpark() public {
|
|
128
128
|
IComments.PermitComment memory permitComment = _createPermitComment(collectorWithToken, "test comment");
|
|
129
129
|
bytes memory signature = _signPermitComment(permitComment, collectorWithTokenPrivateKey);
|
|
130
130
|
|
|
131
131
|
address executor = makeAddr("executor");
|
|
132
132
|
vm.deal(executor, SPARKS_VALUE);
|
|
133
|
-
|
|
133
|
+
|
|
134
134
|
_executePermitComment(executor, permitComment, signature);
|
|
135
135
|
}
|
|
136
136
|
|
|
@@ -90,12 +90,11 @@ contract Comments_smartWallet is CommentsTestBase {
|
|
|
90
90
|
assertTrue(exists);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
function
|
|
93
|
+
function test_commentWithSmartWalletOwner_canCommentWithSpark() public {
|
|
94
94
|
address smartWallet = address(new MockMultiOwnable(address(collectorWithoutToken)));
|
|
95
95
|
|
|
96
96
|
IComments.CommentIdentifier memory emptyReplyTo;
|
|
97
97
|
|
|
98
|
-
vm.expectRevert(abi.encodeWithSelector(IComments.NotTokenHolderOrAdmin.selector));
|
|
99
98
|
vm.prank(collectorWithoutToken);
|
|
100
99
|
vm.deal(collectorWithoutToken, SPARKS_VALUE);
|
|
101
100
|
comments.comment{value: SPARKS_VALUE}({
|
|
@@ -149,4 +148,100 @@ contract Comments_smartWallet is CommentsTestBase {
|
|
|
149
148
|
referrer: address(0)
|
|
150
149
|
});
|
|
151
150
|
}
|
|
151
|
+
|
|
152
|
+
function test_commentWithSmartWalletOwner_whenCoinHolder() public {
|
|
153
|
+
address smartWallet = address(new MockMultiOwnable(address(collectorWithoutToken)));
|
|
154
|
+
|
|
155
|
+
mockCoin.mint(smartWallet, 1e18);
|
|
156
|
+
|
|
157
|
+
IComments.CommentIdentifier memory expectedCommentId = _expectedCommentIdentifier(address(mockCoin), tokenId0, collectorWithoutToken);
|
|
158
|
+
|
|
159
|
+
vm.deal(collectorWithoutToken, SPARKS_VALUE);
|
|
160
|
+
vm.prank(collectorWithoutToken);
|
|
161
|
+
comments.comment{value: SPARKS_VALUE}({
|
|
162
|
+
commenter: collectorWithoutToken,
|
|
163
|
+
contractAddress: address(mockCoin),
|
|
164
|
+
tokenId: tokenId0,
|
|
165
|
+
text: "test comment",
|
|
166
|
+
replyTo: emptyCommentIdentifier,
|
|
167
|
+
commenterSmartWallet: smartWallet,
|
|
168
|
+
referrer: address(0)
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
(, bool exists) = comments.hashAndCheckCommentExists(expectedCommentId);
|
|
172
|
+
assertTrue(exists);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function test_commentWithSmartWalletOwner_whenCoinOwner() public {
|
|
176
|
+
address smartWallet = address(new MockMultiOwnable(address(tokenAdmin)));
|
|
177
|
+
|
|
178
|
+
IComments.CommentIdentifier memory expectedCommentId = _expectedCommentIdentifier(address(mockCoin), tokenId0, tokenAdmin);
|
|
179
|
+
|
|
180
|
+
vm.prank(tokenAdmin);
|
|
181
|
+
comments.comment({
|
|
182
|
+
commenter: tokenAdmin,
|
|
183
|
+
contractAddress: address(mockCoin),
|
|
184
|
+
tokenId: tokenId0,
|
|
185
|
+
text: "test comment",
|
|
186
|
+
replyTo: emptyCommentIdentifier,
|
|
187
|
+
commenterSmartWallet: smartWallet,
|
|
188
|
+
referrer: address(0)
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
(, bool exists) = comments.hashAndCheckCommentExists(expectedCommentId);
|
|
192
|
+
assertTrue(exists);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function test_commentWithSmartWalletOwner_nonCoinHolderCanCommentWithSpark() public {
|
|
196
|
+
address smartWallet = address(new MockMultiOwnable(address(collectorWithoutToken)));
|
|
197
|
+
|
|
198
|
+
vm.prank(collectorWithoutToken);
|
|
199
|
+
vm.deal(collectorWithoutToken, SPARKS_VALUE);
|
|
200
|
+
comments.comment{value: SPARKS_VALUE}({
|
|
201
|
+
commenter: collectorWithoutToken,
|
|
202
|
+
contractAddress: address(mockCoin),
|
|
203
|
+
tokenId: tokenId0,
|
|
204
|
+
text: "test comment",
|
|
205
|
+
replyTo: emptyCommentIdentifier,
|
|
206
|
+
commenterSmartWallet: smartWallet,
|
|
207
|
+
referrer: address(0)
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function test_commentWithSmartWalletOwner_revertsWhenNotCoinSmartWalletOwner() public {
|
|
212
|
+
address smartWallet = address(new MockMultiOwnable(address(makeAddr("notOwner"))));
|
|
213
|
+
|
|
214
|
+
mockCoin.mint(smartWallet, 1e18);
|
|
215
|
+
|
|
216
|
+
vm.expectRevert(IComments.NotSmartWalletOwner.selector);
|
|
217
|
+
vm.prank(collectorWithoutToken);
|
|
218
|
+
vm.deal(collectorWithoutToken, SPARKS_VALUE);
|
|
219
|
+
comments.comment{value: SPARKS_VALUE}({
|
|
220
|
+
commenter: collectorWithoutToken,
|
|
221
|
+
contractAddress: address(mockCoin),
|
|
222
|
+
tokenId: tokenId0,
|
|
223
|
+
text: "test comment",
|
|
224
|
+
replyTo: emptyCommentIdentifier,
|
|
225
|
+
commenterSmartWallet: smartWallet,
|
|
226
|
+
referrer: address(0)
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function test_commentWithSmartWalletOwner_revertsWhenNotCoinSmartWallet() public {
|
|
231
|
+
address smartWallet = makeAddr("smartWallet");
|
|
232
|
+
|
|
233
|
+
mockCoin.mint(smartWallet, 1e18);
|
|
234
|
+
|
|
235
|
+
vm.expectRevert(IComments.NotSmartWallet.selector);
|
|
236
|
+
vm.prank(collectorWithoutToken);
|
|
237
|
+
comments.comment({
|
|
238
|
+
commenter: collectorWithoutToken,
|
|
239
|
+
contractAddress: address(mockCoin),
|
|
240
|
+
tokenId: tokenId0,
|
|
241
|
+
text: "test comment",
|
|
242
|
+
replyTo: emptyCommentIdentifier,
|
|
243
|
+
commenterSmartWallet: smartWallet,
|
|
244
|
+
referrer: address(0)
|
|
245
|
+
});
|
|
246
|
+
}
|
|
152
247
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.20;
|
|
3
|
+
|
|
4
|
+
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
|
5
|
+
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
|
6
|
+
import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol";
|
|
7
|
+
import {ICoinComments} from "../../src/interfaces/ICoinComments.sol";
|
|
8
|
+
|
|
9
|
+
contract MockCoin is ICoinComments, IERC165, ERC20 {
|
|
10
|
+
using EnumerableSet for EnumerableSet.AddressSet;
|
|
11
|
+
|
|
12
|
+
address internal _payoutRecipient;
|
|
13
|
+
EnumerableSet.AddressSet internal _owners;
|
|
14
|
+
|
|
15
|
+
constructor(address payoutRecipient_, address[] memory owners_) ERC20("MockCoin", "MC") {
|
|
16
|
+
_payoutRecipient = payoutRecipient_;
|
|
17
|
+
|
|
18
|
+
for (uint256 i; i < owners_.length; i++) {
|
|
19
|
+
_owners.add(owners_[i]);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function mint(address to, uint256 amount) public {
|
|
24
|
+
_mint(to, amount);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function balanceOf(address account) public view override(ERC20, ICoinComments) returns (uint256) {
|
|
28
|
+
return super.balanceOf(account);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function payoutRecipient() public view override(ICoinComments) returns (address) {
|
|
32
|
+
return _payoutRecipient;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isOwner(address account) public view override(ICoinComments) returns (bool) {
|
|
36
|
+
return _owners.contains(account);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function supportsInterface(bytes4 interfaceId) public pure returns (bool) {
|
|
40
|
+
return type(ICoinComments).interfaceId == interfaceId;
|
|
41
|
+
}
|
|
42
|
+
}
|
package/script/bundle-abis.ts
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { promises as fs } from "fs";
|
|
2
|
-
import { basename, extname, join, resolve } from "pathe";
|
|
3
|
-
import { glob } from "glob";
|
|
4
|
-
import { ContractConfig } from "@wagmi/cli";
|
|
5
|
-
|
|
6
|
-
const defaultExcludes = [
|
|
7
|
-
"Common.sol/**",
|
|
8
|
-
"Components.sol/**",
|
|
9
|
-
"Script.sol/**",
|
|
10
|
-
"StdAssertions.sol/**",
|
|
11
|
-
"StdInvariant.sol/**",
|
|
12
|
-
"StdError.sol/**",
|
|
13
|
-
"StdCheats.sol/**",
|
|
14
|
-
"StdMath.sol/**",
|
|
15
|
-
"StdJson.sol/**",
|
|
16
|
-
"StdStorage.sol/**",
|
|
17
|
-
"StdUtils.sol/**",
|
|
18
|
-
"Vm.sol/**",
|
|
19
|
-
"console.sol/**",
|
|
20
|
-
"console2.sol/**",
|
|
21
|
-
"test.sol/**",
|
|
22
|
-
"**.s.sol/*.json",
|
|
23
|
-
"**.t.sol/*.json",
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
// design inspired by https://github.com/wagmi-dev/wagmi/blob/main/packages/cli/src/plugins/foundry.ts
|
|
27
|
-
|
|
28
|
-
export const readContracts = async ({
|
|
29
|
-
deployments = {} as any,
|
|
30
|
-
exclude = defaultExcludes,
|
|
31
|
-
include = ["*.json"],
|
|
32
|
-
namePrefix = "",
|
|
33
|
-
project_ = "./",
|
|
34
|
-
}) => {
|
|
35
|
-
// get all the files in ./out
|
|
36
|
-
function getContractName(artifactPath: string, usePrefix = true) {
|
|
37
|
-
const filename = basename(artifactPath);
|
|
38
|
-
const extension = extname(artifactPath);
|
|
39
|
-
return `${usePrefix ? namePrefix : ""}${filename.replace(extension, "")}`;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function getContract(artifactPath: string) {
|
|
43
|
-
const artifact = JSON.parse(await fs.readFile(artifactPath, "utf-8"));
|
|
44
|
-
return {
|
|
45
|
-
abi: artifact.abi,
|
|
46
|
-
address: (deployments as Record<string, ContractConfig["address"]>)[
|
|
47
|
-
getContractName(artifactPath, false)
|
|
48
|
-
],
|
|
49
|
-
name: getContractName(artifactPath),
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
async function getArtifactPaths(artifactsDirectory: string) {
|
|
54
|
-
return await glob([
|
|
55
|
-
...include.map((x) => `${artifactsDirectory}/**/${x}`),
|
|
56
|
-
...exclude.map((x) => `!${artifactsDirectory}/**/${x}`),
|
|
57
|
-
]);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const project = resolve(process.cwd(), project_ ?? "");
|
|
61
|
-
|
|
62
|
-
const config = {
|
|
63
|
-
out: "out",
|
|
64
|
-
src: "src",
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const artifactsDirectory = join(project, config.out);
|
|
68
|
-
|
|
69
|
-
const artifactPaths = await getArtifactPaths(artifactsDirectory);
|
|
70
|
-
const contracts = [];
|
|
71
|
-
for (const artifactPath of artifactPaths) {
|
|
72
|
-
const contract = await getContract(artifactPath);
|
|
73
|
-
if (!contract.abi?.length) continue;
|
|
74
|
-
contracts.push(contract);
|
|
75
|
-
}
|
|
76
|
-
return contracts;
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
async function saveContractsAbisJson(contracts: { abi: any; name: string }[]) {
|
|
80
|
-
// for each contract, write abi to ./abis/{contractName}.json
|
|
81
|
-
|
|
82
|
-
const abisFolder = "./abis";
|
|
83
|
-
|
|
84
|
-
// mkdir - p ./abis:
|
|
85
|
-
await fs.mkdir(abisFolder, { recursive: true });
|
|
86
|
-
// remove abis folder:
|
|
87
|
-
await fs.rm(abisFolder, { recursive: true });
|
|
88
|
-
// add it back
|
|
89
|
-
// mkdir - p ./abis:
|
|
90
|
-
await fs.mkdir(abisFolder, { recursive: true });
|
|
91
|
-
|
|
92
|
-
// now write abis:
|
|
93
|
-
await Promise.all(
|
|
94
|
-
contracts.map(async (contract) => {
|
|
95
|
-
const abiJson = JSON.stringify(contract.abi, null, 2);
|
|
96
|
-
const abiJsonPath = `${abisFolder}/${contract.name}.json`;
|
|
97
|
-
|
|
98
|
-
await fs.writeFile(abiJsonPath, abiJson);
|
|
99
|
-
}),
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async function main() {
|
|
104
|
-
const contracts = await readContracts({});
|
|
105
|
-
|
|
106
|
-
await saveContractsAbisJson(contracts);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
main();
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import { promisify } from "util";
|
|
4
|
-
import { fileURLToPath } from "url";
|
|
5
|
-
|
|
6
|
-
const readFileAsync = promisify(fs.readFile);
|
|
7
|
-
const writeFileAsync = promisify(fs.writeFile);
|
|
8
|
-
|
|
9
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
-
const __dirname = path.dirname(__filename);
|
|
11
|
-
|
|
12
|
-
const makePackageVersionFile = async (version: string): Promise<void> => {
|
|
13
|
-
console.log("updating contract version to ", version);
|
|
14
|
-
// read the version from the root package.json:
|
|
15
|
-
|
|
16
|
-
const packageVersionCode = `// This file is automatically generated by code; do not manually update
|
|
17
|
-
// SPDX-License-Identifier: MIT
|
|
18
|
-
pragma solidity ^0.8.23;
|
|
19
|
-
|
|
20
|
-
import {IVersionedContract} from "@zoralabs/shared-contracts/interfaces/IVersionedContract.sol";
|
|
21
|
-
|
|
22
|
-
/// @title ContractVersionBase
|
|
23
|
-
/// @notice Base contract for versioning contracts
|
|
24
|
-
contract ContractVersionBase is IVersionedContract {
|
|
25
|
-
/// @notice The version of the contract
|
|
26
|
-
function contractVersion() external pure override returns (string memory) {
|
|
27
|
-
return "${version}";
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
`;
|
|
31
|
-
|
|
32
|
-
// write the file to __dirname__/../src/version/ContractVersionBase.sol:
|
|
33
|
-
const filePath = path.join(
|
|
34
|
-
__dirname,
|
|
35
|
-
"..",
|
|
36
|
-
"src",
|
|
37
|
-
"version",
|
|
38
|
-
"ContractVersionBase.sol",
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
console.log("generated contract version code:", packageVersionCode);
|
|
42
|
-
console.log("writing file to", filePath);
|
|
43
|
-
|
|
44
|
-
await writeFileAsync(filePath, packageVersionCode);
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const getVersion = async (): Promise<string> => {
|
|
48
|
-
// read package.json file, parse json, then get version:
|
|
49
|
-
const packageJson = JSON.parse(
|
|
50
|
-
await readFileAsync(path.join(__dirname, "..", "package.json"), "utf-8"),
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
return packageJson.version;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const main = async (): Promise<void> => {
|
|
57
|
-
const version = await getVersion();
|
|
58
|
-
await makePackageVersionFile(version);
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
main().catch((error) => {
|
|
62
|
-
console.error("Error updating contract version:", error);
|
|
63
|
-
});
|