diff --git a/.gitignore b/.gitignore index 11ebfea6..df40ead6 100644 --- a/.gitignore +++ b/.gitignore @@ -133,3 +133,5 @@ dist deployment-zk/* !deployment-zk/lensSepoliaTestnet/ cache_forge/solidity-files-cache.json + +lcov.info diff --git a/contracts/actions/account/TippingAccountAction.sol b/contracts/actions/account/TippingAccountAction.sol index 253732d8..67d2e3dc 100644 --- a/contracts/actions/account/TippingAccountAction.sol +++ b/contracts/actions/account/TippingAccountAction.sol @@ -1,12 +1,13 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {BaseAccountAction} from "contracts/actions/account/base/BaseAccountAction.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {KeyValue} from "contracts/core/types/Types.sol"; import {MetadataBased} from "contracts/core/base/MetadataBased.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; contract TippingAccountAction is BaseAccountAction, MetadataBased { using SafeERC20 for IERC20; @@ -40,7 +41,7 @@ contract TippingAccountAction is BaseAccountAction, MetadataBased { erc20Token = abi.decode(params[i].value, (address)); } } - require(tipAmount > 0); + require(tipAmount > 0, Errors.InvalidParameter()); IERC20(erc20Token).safeTransferFrom(originalMsgSender, account, tipAmount); return ""; } diff --git a/contracts/actions/account/base/BaseAccountAction.sol b/contracts/actions/account/base/BaseAccountAction.sol index 6a179e78..63ebcbbd 100644 --- a/contracts/actions/account/base/BaseAccountAction.sol +++ b/contracts/actions/account/base/BaseAccountAction.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {KeyValue} from "contracts/core/types/Types.sol"; import {BaseAction} from "contracts/actions/base/BaseAction.sol"; import {IAccountAction} from "contracts/extensions/actions/ActionHub.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; abstract contract BaseAccountAction is BaseAction, IAccountAction { constructor(address actionHub) BaseAction(actionHub) {} @@ -55,6 +56,6 @@ abstract contract BaseAccountAction is BaseAction, IAccountAction { bool, /* isDisabled */ KeyValue[] calldata /* params */ ) internal virtual returns (bytes memory) { - revert(); + revert Errors.NotImplemented(); } } diff --git a/contracts/actions/base/BaseAction.sol b/contracts/actions/base/BaseAction.sol index fe7b91bc..61f00d4d 100644 --- a/contracts/actions/base/BaseAction.sol +++ b/contracts/actions/base/BaseAction.sol @@ -1,8 +1,9 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {UNIVERSAL_ACTION_MAGIC_VALUE} from "contracts/extensions/actions/ActionHub.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; abstract contract BaseAction { address immutable ACTION_HUB; @@ -11,7 +12,7 @@ abstract contract BaseAction { bytes32 constant STORAGE__ACTION_CONFIGURED = 0x852bead036b7ef35b8026346140cc688bafe817a6c3491812e6d994b1bcda6d9; modifier onlyActionHub() { - require(msg.sender == ACTION_HUB); + require(msg.sender == ACTION_HUB, Errors.InvalidMsgSender()); _; } @@ -24,8 +25,8 @@ abstract contract BaseAction { assembly { configured := sload(STORAGE__ACTION_CONFIGURED) } - require(!configured); - require(originalMsgSender == address(0)); + require(!configured, Errors.RedundantStateChange()); + require(originalMsgSender == address(0), Errors.InvalidParameter()); assembly { sstore(STORAGE__ACTION_CONFIGURED, 1) } diff --git a/contracts/actions/post/TippingPostAction.sol b/contracts/actions/post/TippingPostAction.sol index 8cfba0b9..8766d1b2 100644 --- a/contracts/actions/post/TippingPostAction.sol +++ b/contracts/actions/post/TippingPostAction.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {BasePostAction} from "contracts/actions/post/base/BasePostAction.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; @@ -8,6 +8,7 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol import {KeyValue} from "contracts/core/types/Types.sol"; import {IFeed} from "contracts/core/interfaces/IFeed.sol"; import {MetadataBased} from "contracts/core/base/MetadataBased.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; contract TippingPostAction is BasePostAction, MetadataBased { using SafeERC20 for IERC20; @@ -41,7 +42,7 @@ contract TippingPostAction is BasePostAction, MetadataBased { erc20Token = abi.decode(params[i].value, (address)); } } - require(tipAmount > 0); + require(tipAmount > 0, Errors.InvalidParameter()); address account = IFeed(feed).getPostAuthor(postId); IERC20(erc20Token).safeTransferFrom(originalMsgSender, account, tipAmount); return abi.encode(account); diff --git a/contracts/actions/post/base/BasePostAction.sol b/contracts/actions/post/base/BasePostAction.sol index 2dc1bcfb..ebd50b80 100644 --- a/contracts/actions/post/base/BasePostAction.sol +++ b/contracts/actions/post/base/BasePostAction.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {KeyValue} from "contracts/core/types/Types.sol"; import {BaseAction} from "contracts/actions/base/BaseAction.sol"; import {IPostAction} from "contracts/extensions/actions/ActionHub.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; abstract contract BasePostAction is BaseAction, IPostAction { constructor(address actionHub) BaseAction(actionHub) {} @@ -58,6 +59,6 @@ abstract contract BasePostAction is BaseAction, IPostAction { bool, /* isDisabled */ KeyValue[] calldata /* params */ ) internal virtual returns (bytes memory) { - revert(); + revert Errors.NotImplemented(); } } diff --git a/contracts/actions/post/collect/IERC7572.sol b/contracts/actions/post/collect/IERC7572.sol index efb9d714..0c148614 100644 --- a/contracts/actions/post/collect/IERC7572.sol +++ b/contracts/actions/post/collect/IERC7572.sol @@ -2,7 +2,7 @@ // Copyright (C) 2024 Lens Labs. All Rights Reserved. // As appears in https://eips.ethereum.org/EIPS/eip-7572 -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; /** * This specification standardizes contractURI() to return contract-level metadata. This is useful for dapps and diff --git a/contracts/actions/post/collect/ISimpleCollectAction.sol b/contracts/actions/post/collect/ISimpleCollectAction.sol index 2d812d0a..6967c968 100644 --- a/contracts/actions/post/collect/ISimpleCollectAction.sol +++ b/contracts/actions/post/collect/ISimpleCollectAction.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IPostAction} from "contracts/extensions/actions/ActionHub.sol"; diff --git a/contracts/actions/post/collect/LensCollectedPost.sol b/contracts/actions/post/collect/LensCollectedPost.sol index b3895fcd..a1ca5ae7 100644 --- a/contracts/actions/post/collect/LensCollectedPost.sol +++ b/contracts/actions/post/collect/LensCollectedPost.sol @@ -1,11 +1,12 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import "contracts/core/base/LensERC721.sol"; import {IERC7572} from "contracts/actions/post/collect/IERC7572.sol"; import {IFeed} from "contracts/core/interfaces/IFeed.sol"; import {ITokenURIProvider} from "contracts/core/interfaces/ITokenURIProvider.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; /** * @notice A contract that represents a Lens Collected Post. @@ -24,16 +25,14 @@ contract LensCollectedPost is LensERC721, IERC7572 { string internal _contractURI; address internal immutable _feed; uint256 internal immutable _postId; - bool internal immutable _isImmutable; // TODO: This can be replaced with bytes(_contentURISnapshot).length address internal immutable _collectAction; constructor(address feed, uint256 postId, bool isImmutable) { LensERC721._initialize("Lens Collected Post", "LCP", ITokenURIProvider(address(0))); string memory contentURI = IFeed(feed).getPost(postId).contentURI; - require(bytes(contentURI).length > 0, "Post content URI is empty"); + require(bytes(contentURI).length > 0, Errors.InvalidParameter()); _feed = feed; _postId = postId; - _isImmutable = isImmutable; _contractURI = contentURI; _collectAction = msg.sender; if (isImmutable) { @@ -43,7 +42,7 @@ contract LensCollectedPost is LensERC721, IERC7572 { } function mint(address to, uint256 tokenId) external { - require(msg.sender == _collectAction, "Only CollectAction can mint"); + require(msg.sender == _collectAction, Errors.InvalidMsgSender()); _mint(to, tokenId); } @@ -54,12 +53,12 @@ contract LensCollectedPost is LensERC721, IERC7572 { } function tokenURI(uint256 /*tokenId*/ ) public view override returns (string memory) { - if (_isImmutable) { + if (bytes(_contentURISnapshot).length > 0) { return _contentURISnapshot; } else { string memory contentURI = IFeed(_feed).getPost(_postId).contentURI; // TODO: If content was deleted - should we fail or return empty string? - require(bytes(contentURI).length > 0); + require(bytes(contentURI).length > 0, Errors.DoesNotExist()); return contentURI; } } @@ -69,6 +68,6 @@ contract LensCollectedPost is LensERC721, IERC7572 { // Disabling integrated LensERC721 tokenURIProvider // TODO: Is this approach more favorable than deploying the LensCollectedPostTokenURIProvider over and over? function _beforeTokenURIProviderSet(ITokenURIProvider /* tokenURIProvider */ ) internal pure override { - revert(); + revert Errors.NotImplemented(); } } diff --git a/contracts/actions/post/collect/SimpleCollectAction.sol b/contracts/actions/post/collect/SimpleCollectAction.sol index 441f6986..8a6506f6 100644 --- a/contracts/actions/post/collect/SimpleCollectAction.sol +++ b/contracts/actions/post/collect/SimpleCollectAction.sol @@ -1,18 +1,17 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {ISimpleCollectAction, CollectActionData} from "contracts/actions/post/collect/ISimpleCollectAction.sol"; import {IFeed} from "contracts/core/interfaces/IFeed.sol"; import {IGraph} from "contracts/core/interfaces/IGraph.sol"; - import {LensCollectedPost} from "contracts/actions/post/collect/LensCollectedPost.sol"; import {BasePostAction} from "contracts/actions/post/base/BasePostAction.sol"; - import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {MetadataBased} from "contracts/core/base/MetadataBased.sol"; import {KeyValue} from "contracts/core/types/Types.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; contract SimpleCollectAction is ISimpleCollectAction, BasePostAction, MetadataBased { using SafeERC20 for IERC20; @@ -107,7 +106,7 @@ contract SimpleCollectAction is ISimpleCollectAction, BasePostAction, MetadataBa // Editing existing collect action config if (storedData.isImmutable) { // TODO: Should we have two different bools? isImmutableConfig & isImmutableContentURI? - revert("Cannot edit immutable collect"); + revert Errors.Immutable(); } else { storedData.amount = configData.amount; storedData.collectLimit = configData.collectLimit; @@ -152,9 +151,9 @@ contract SimpleCollectAction is ISimpleCollectAction, BasePostAction, MetadataBa _validateSenderIsAuthor(originalMsgSender, feed, postId); CollectActionData storage storedData = $collectDataStorage().collectData[feed][postId]; // We don't check for existence of collect before disabling, because it might be useful to disable it initially - // require(storedData.collectionAddress != address(0), "Collect not configured for this post"); - require(!storedData.isImmutable, "Cannot modify immutable collect"); - require(storedData.isDisabled != isDisabled, "Already in desired state"); + // require(storedData.collectionAddress != address(0), Errors.DoesNotExist()); + require(!storedData.isImmutable, Errors.Immutable()); + require(storedData.isDisabled != isDisabled, Errors.RedundantStateChange()); storedData.isDisabled = isDisabled; return abi.encode(isDisabled); } @@ -165,18 +164,18 @@ contract SimpleCollectAction is ISimpleCollectAction, BasePostAction, MetadataBa function _validateSenderIsAuthor(address sender, address feed, uint256 postId) internal virtual { if (sender != IFeed(feed).getPostAuthor(postId)) { - revert("Sender is not the author"); + revert Errors.InvalidMsgSender(); } } function _validateConfigureParams(CollectActionConfigureParams memory configData) internal virtual { if (configData.amount == 0) { - require(configData.token == address(0), "Invalid token"); + require(configData.token == address(0), Errors.InvalidParameter()); } else { - require(configData.token != address(0), "Invalid token"); + require(configData.token != address(0), Errors.InvalidParameter()); } if (configData.endTimestamp != 0 && configData.endTimestamp < block.timestamp) { - revert("Invalid params"); + revert Errors.InvalidParameter(); } if (configData.followerOnlyGraph != address(0)) { // Check if the Graph supports isFollowing() interface with two random addresses @@ -209,24 +208,24 @@ contract SimpleCollectAction is ISimpleCollectAction, BasePostAction, MetadataBa ) internal virtual { CollectActionData storage data = $collectDataStorage().collectData[feed][postId]; - require(data.collectionAddress != address(0), "Collect not configured for this post"); + require(data.collectionAddress != address(0), Errors.DoesNotExist()); if (data.endTimestamp != 0 && block.timestamp > data.endTimestamp) { - revert("Collect expired"); + revert Errors.Expired(); } if (data.collectLimit != 0 && data.currentCollects + 1 > data.collectLimit) { - revert("Collect limit exceeded"); + revert Errors.LimitReached(); } if (expectedParams.amount != data.amount || expectedParams.token != data.token) { - revert("Invalid expected amount and/or token"); + revert Errors.InvalidParameter(); } if (data.followerOnlyGraph != address(0)) { require( IGraph(data.followerOnlyGraph).isFollowing(originalMsgSender, IFeed(feed).getPostAuthor(postId)), - "Not following" + Errors.NotFollowing() ); } @@ -236,12 +235,12 @@ contract SimpleCollectAction is ISimpleCollectAction, BasePostAction, MetadataBa require( keccak256(bytes(contentURI)) == keccak256(bytes(LensCollectedPost(data.collectionAddress).tokenURI(data.currentCollects))), - "Invalid content URI" + Errors.InvalidParameter() ); } if (data.isDisabled) { - revert("Collect is disabled"); + revert Errors.Disabled(); } } diff --git a/contracts/core/access/AccessControlled.sol b/contracts/core/access/AccessControlled.sol index d25ee3a1..13bee0c2 100644 --- a/contracts/core/access/AccessControlled.sol +++ b/contracts/core/access/AccessControlled.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IAccessControl} from "contracts/core/interfaces/IAccessControl.sol"; import {AccessControlLib} from "contracts/core/libraries/AccessControlLib.sol"; diff --git a/contracts/core/access/Ownable.sol b/contracts/core/access/Ownable.sol index 60441bfd..1b2d6c33 100644 --- a/contracts/core/access/Ownable.sol +++ b/contracts/core/access/Ownable.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.12; +pragma solidity ^0.8.26; + +import {Errors} from "contracts/core/types/Errors.sol"; abstract contract Ownable { event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); @@ -19,7 +21,7 @@ abstract contract Ownable { } modifier onlyOwner() { - require(msg.sender == $ownableStorage().owner); + require(msg.sender == $ownableStorage().owner, Errors.InvalidMsgSender()); _; } diff --git a/contracts/core/access/RoleBasedAccessControl.sol b/contracts/core/access/RoleBasedAccessControl.sol index c6f72ce7..85b84214 100644 --- a/contracts/core/access/RoleBasedAccessControl.sol +++ b/contracts/core/access/RoleBasedAccessControl.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {Access, IRoleBasedAccessControl} from "contracts/core/interfaces/IRoleBasedAccessControl.sol"; import {Events} from "contracts/core/types/Events.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; /** * This Access Control: @@ -40,7 +41,7 @@ contract RoleBasedAccessControl is IRoleBasedAccessControl { function transferOwnership(address newOwner) external virtual { address oldOwner = _owner; - require(msg.sender == oldOwner, "Only owner can transfer ownership"); + require(msg.sender == oldOwner, Errors.InvalidMsgSender()); _owner = newOwner; _revokeRole(oldOwner, OWNER_ROLE_ID); _grantRole(newOwner, OWNER_ROLE_ID); @@ -77,12 +78,12 @@ contract RoleBasedAccessControl is IRoleBasedAccessControl { } function _beforeGrantingRole(address, /* account */ uint256 roleId) internal virtual { - require(msg.sender == _owner, "Only owner can assign roles"); - require(roleId != OWNER_ROLE_ID, "Cannot grant owner role"); + require(msg.sender == _owner, Errors.InvalidMsgSender()); + require(roleId != OWNER_ROLE_ID, Errors.InvalidParameter()); } function _grantRole(address account, uint256 roleId) internal virtual { - require(!_hasRole(account, roleId)); + require(!_hasRole(account, roleId), Errors.RedundantStateChange()); _roles[account].push(roleId); emit Lens_AccessControl_RoleGranted(account, roleId); } @@ -93,13 +94,13 @@ contract RoleBasedAccessControl is IRoleBasedAccessControl { } function _beforeRevokingRole(address, /* account */ uint256 roleId) internal virtual { - require(msg.sender == _owner, "Only owner can revoke roles"); - require(roleId != OWNER_ROLE_ID, "Cannot revoke owner role"); + require(msg.sender == _owner, Errors.InvalidMsgSender()); + require(roleId != OWNER_ROLE_ID, Errors.InvalidParameter()); } function _revokeRole(address account, uint256 roleId) internal virtual { uint256 accountRolesLength = _roles[account].length; - require(accountRolesLength > 0); + require(accountRolesLength > 0, Errors.RedundantStateChange()); uint256 roleIndex = 0; while (roleIndex < accountRolesLength) { if (_roles[account][roleIndex] == roleId) { @@ -108,7 +109,7 @@ contract RoleBasedAccessControl is IRoleBasedAccessControl { roleIndex++; } } - require(roleIndex < accountRolesLength); // Index must be found before reaching the end of the array + require(roleIndex < accountRolesLength, Errors.RedundantStateChange()); // Index must be found before reaching the end of the array _roles[account][roleIndex] = _roles[account][accountRolesLength - 1]; _roles[account].pop(); emit Lens_AccessControl_RoleRevoked(account, roleId); @@ -133,15 +134,15 @@ contract RoleBasedAccessControl is IRoleBasedAccessControl { uint256, /* permissionId */ Access /* access */ ) internal virtual { - require(msg.sender == _owner, "Only owner can set access"); - require(roleId != OWNER_ROLE_ID, "Cannot set access for owner role"); + require(msg.sender == _owner, Errors.InvalidMsgSender()); + require(roleId != OWNER_ROLE_ID, Errors.InvalidParameter()); } function _setAccess(uint256 roleId, address contractAddress, uint256 permissionId, Access access) internal virtual { Access perviousAccess = _access[roleId][contractAddress][permissionId]; _access[roleId][contractAddress][permissionId] = access; if (perviousAccess == Access.UNDEFINED) { - require(access != Access.UNDEFINED); + require(access != Access.UNDEFINED, Errors.RedundantStateChange()); emit Lens_AccessControl_AccessAdded(roleId, contractAddress, permissionId, access == Access.GRANTED); } else if (access == Access.UNDEFINED) { emit Lens_AccessControl_AccessRemoved(roleId, contractAddress, permissionId); @@ -156,6 +157,8 @@ contract RoleBasedAccessControl is IRoleBasedAccessControl { virtual returns (bool) { + require(contractAddress != ANY_CONTRACT_ADDRESS, Errors.InvalidParameter()); + require(permissionId != ANY_PERMISSION_ID, Errors.InvalidParameter()); for (uint256 i = 0; i < _roles[account].length; i++) { if (_hasAccess(_roles[account][i], contractAddress, permissionId)) { // GRANTED-overrides strategy @@ -171,9 +174,6 @@ contract RoleBasedAccessControl is IRoleBasedAccessControl { virtual returns (bool) { - require(contractAddress != ANY_CONTRACT_ADDRESS); - require(permissionId != ANY_PERMISSION_ID); - Access fullySpecifiedAccess = _access[roleId][contractAddress][permissionId]; if (fullySpecifiedAccess != Access.UNDEFINED) { diff --git a/contracts/core/base/BaseSource.sol b/contracts/core/base/BaseSource.sol index 2e9a7f3f..410a2b77 100644 --- a/contracts/core/base/BaseSource.sol +++ b/contracts/core/base/BaseSource.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {ISource} from "contracts/core/interfaces/ISource.sol"; import {SourceStamp} from "contracts/core/types/Types.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; abstract contract BaseSource is ISource { bytes2 internal immutable EIP191_VERSION_BYTE_0X01_HEADER = 0x1901; @@ -21,9 +22,9 @@ abstract contract BaseSource is ISource { // Signature Standard: EIP-191 - Version Byte: 0x00 function _validateSource(SourceStamp calldata sourceStamp) internal virtual { - require(!_wasSourceStampNonceUsed[sourceStamp.nonce]); - require(sourceStamp.deadline >= block.timestamp); - require(sourceStamp.source == address(this)); + require(!_wasSourceStampNonceUsed[sourceStamp.nonce], Errors.NonceUsed()); + require(sourceStamp.deadline >= block.timestamp, Errors.Expired()); + require(sourceStamp.source == address(this), Errors.InvalidParameter()); _wasSourceStampNonceUsed[sourceStamp.nonce] = true; bytes32 digest = _calculateDigest(_calculateHashStruct(sourceStamp)); bytes32 r; @@ -36,7 +37,7 @@ abstract contract BaseSource is ISource { v := byte(0, mload(add(signature, 0x60))) } address signer = ecrecover(digest, v, r, s); - require(_isValidSourceStampSigner(signer)); + require(_isValidSourceStampSigner(signer), Errors.WrongSigner()); } function _isValidSourceStampSigner(address signer) internal virtual returns (bool); diff --git a/contracts/core/base/ExtraStorageBased.sol b/contracts/core/base/ExtraStorageBased.sol index 355c0e14..0754e930 100644 --- a/contracts/core/base/ExtraStorageBased.sol +++ b/contracts/core/base/ExtraStorageBased.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {KeyValue} from "contracts/core/types/Types.sol"; import {ExtraDataLib} from "contracts/core/libraries/ExtraDataLib.sol"; diff --git a/contracts/core/base/LensERC721.sol b/contracts/core/base/LensERC721.sol index ee324d13..80ef8902 100644 --- a/contracts/core/base/LensERC721.sol +++ b/contracts/core/base/LensERC721.sol @@ -1,13 +1,14 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. // Modified from OpenZeppelin's v4.9.0 contracts -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import "contracts/core/interfaces/IERC721.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721ReceiverUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; import "contracts/core/interfaces/ITokenURIProvider.sol"; import "contracts/core/interfaces/IERC4906Events.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; abstract contract LensERC721 is IERC721 { using AddressUpgradeable for address; @@ -51,7 +52,7 @@ abstract contract LensERC721 is IERC721 { * @dev See {IERC721-balanceOf}. */ function balanceOf(address owner) public view virtual override returns (uint256) { - require(owner != address(0), "ERC721: address zero is not a valid owner"); + require(owner != address(0), Errors.InvalidParameter()); return $erc721Storage().balances[owner]; } @@ -60,7 +61,7 @@ abstract contract LensERC721 is IERC721 { */ function ownerOf(uint256 tokenId) public view virtual override returns (address) { address owner = _ownerOf(tokenId); - require(owner != address(0), "ERC721: invalid token ID"); + require(owner != address(0), Errors.DoesNotExist()); return owner; } @@ -99,12 +100,9 @@ abstract contract LensERC721 is IERC721 { */ function approve(address to, uint256 tokenId) public virtual override { address owner = LensERC721.ownerOf(tokenId); - require(to != owner, "ERC721: approval to current owner"); + require(to != owner, Errors.InvalidParameter()); - require( - msg.sender == owner || isApprovedForAll(owner, msg.sender), - "ERC721: approve caller is not token owner or approved for all" - ); + require(msg.sender == owner || isApprovedForAll(owner, msg.sender), Errors.InvalidMsgSender()); _approve(to, tokenId); } @@ -137,7 +135,7 @@ abstract contract LensERC721 is IERC721 { */ function transferFrom(address from, address to, uint256 tokenId) public virtual override { //solhint-disable-next-line max-line-length - require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: caller is not token owner or approved"); + require(_isApprovedOrOwner(msg.sender, tokenId), Errors.InvalidMsgSender()); _transfer(from, to, tokenId); } @@ -153,7 +151,7 @@ abstract contract LensERC721 is IERC721 { * @dev See {IERC721-safeTransferFrom}. */ function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual override { - require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: caller is not token owner or approved"); + require(_isApprovedOrOwner(msg.sender, tokenId), Errors.InvalidMsgSender()); _safeTransfer(from, to, tokenId, data); } @@ -177,7 +175,7 @@ abstract contract LensERC721 is IERC721 { */ function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual { _transfer(from, to, tokenId); - require(_checkOnERC721Received(from, to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer"); + require(_checkOnERC721Received(from, to, tokenId, data), Errors.UnexpectedContractImpl()); } /** @@ -231,9 +229,7 @@ abstract contract LensERC721 is IERC721 { */ function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual { _mint(to, tokenId); - require( - _checkOnERC721Received(address(0), to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer" - ); + require(_checkOnERC721Received(address(0), to, tokenId, data), Errors.UnexpectedContractImpl()); } /** @@ -249,13 +245,13 @@ abstract contract LensERC721 is IERC721 { * Emits a {Transfer} event. */ function _mint(address to, uint256 tokenId) internal virtual { - require(to != address(0), "ERC721: mint to the zero address"); - require(!_exists(tokenId), "ERC721: token already minted"); + require(to != address(0), Errors.InvalidParameter()); + require(!_exists(tokenId), Errors.AlreadyExists()); _beforeTokenTransfer(address(0), to, tokenId); // Check that tokenId was not minted by `_beforeTokenTransfer` hook - require(!_exists(tokenId), "ERC721: token already minted"); + require(!_exists(tokenId), Errors.AlreadyExists()); unchecked { // Will not overflow unless all 2**256 token ids are minted to the same owner. @@ -318,13 +314,13 @@ abstract contract LensERC721 is IERC721 { * Emits a {Transfer} event. */ function _transfer(address from, address to, uint256 tokenId) internal virtual { - require(LensERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner"); - require(to != address(0), "ERC721: transfer to the zero address"); + require(LensERC721.ownerOf(tokenId) == from, Errors.InvalidParameter()); + require(to != address(0), Errors.InvalidParameter()); _beforeTokenTransfer(from, to, tokenId); // Check that tokenId was not transferred by `_beforeTokenTransfer` hook - require(LensERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner"); + require(LensERC721.ownerOf(tokenId) == from, Errors.InvalidParameter()); // Clear approvals from the previous owner delete $erc721Storage().tokenApprovals[tokenId]; @@ -361,7 +357,7 @@ abstract contract LensERC721 is IERC721 { * Emits an {ApprovalForAll} event. */ function _setApprovalForAll(address owner, address operator, bool approved) internal virtual { - require(owner != operator, "ERC721: approve to caller"); + require(owner != operator, Errors.InvalidParameter()); $erc721Storage().operatorApprovals[owner][operator] = approved; emit ApprovalForAll(owner, operator, approved); } @@ -370,7 +366,7 @@ abstract contract LensERC721 is IERC721 { * @dev Reverts if the `tokenId` has not been minted yet. */ function _requireMinted(uint256 tokenId) internal view virtual { - require(_exists(tokenId), "ERC721: invalid token ID"); + require(_exists(tokenId), Errors.DoesNotExist()); } /** @@ -393,7 +389,7 @@ abstract contract LensERC721 is IERC721 { return retval == IERC721ReceiverUpgradeable.onERC721Received.selector; } catch (bytes memory reason) { if (reason.length == 0) { - revert("ERC721: transfer to non ERC721Receiver implementer"); + revert Errors.UnexpectedContractImpl(); } else { /// @solidity memory-safe-assembly assembly { diff --git a/contracts/core/base/MetadataBased.sol b/contracts/core/base/MetadataBased.sol index ccfc0ac2..085fea8f 100644 --- a/contracts/core/base/MetadataBased.sol +++ b/contracts/core/base/MetadataBased.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; +pragma solidity ^0.8.26; import {IMetadataBased} from "contracts/core/interfaces/IMetadataBased.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; abstract contract MetadataBased is IMetadataBased { struct MetadataURIStorage { @@ -33,7 +34,7 @@ abstract contract MetadataBased is IMetadataBased { } function _beforeMetadataURIUpdate(string memory /* metadataURI */ ) internal virtual { - revert(); + revert Errors.NotImplemented(); } function _emitMetadataURISet(string memory /* metadataURI */ ) internal virtual; diff --git a/contracts/core/base/RuleBasedPrimitive.sol b/contracts/core/base/RuleBasedPrimitive.sol index 4b6840ed..096646e0 100644 --- a/contracts/core/base/RuleBasedPrimitive.sol +++ b/contracts/core/base/RuleBasedPrimitive.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {RulesStorage, RulesLib} from "contracts/core/libraries/RulesLib.sol"; import { @@ -12,12 +12,13 @@ import { KeyValue } from "contracts/core/types/Types.sol"; import {CallLib} from "contracts/core/libraries/CallLib.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; abstract contract RuleBasedPrimitive { using RulesLib for RulesStorage; using CallLib for address; - function _changePrimitiveRules(RulesStorage storage rulesStorage, RuleChange[] calldata ruleChanges) + function _changePrimitiveRules(RulesStorage storage rulesStorage, RuleChange[] memory ruleChanges) internal virtual { @@ -35,8 +36,8 @@ abstract contract RuleBasedPrimitive { function _changeEntityRules( RulesStorage storage rulesStorage, uint256 entityId, - RuleChange[] calldata ruleChanges, - RuleProcessingParams[] calldata ruleChangesProcessingParams + RuleChange[] memory ruleChanges, + RuleProcessingParams[] memory ruleChangesProcessingParams ) internal virtual { _changeRules( rulesStorage, @@ -49,7 +50,7 @@ abstract contract RuleBasedPrimitive { ); } - function _encodeConfigureCall(uint256 entityId, bytes32 configSalt, KeyValue[] calldata ruleParams) + function _encodeConfigureCall(uint256 entityId, bytes32 configSalt, KeyValue[] memory ruleParams) internal pure returns (bytes memory) @@ -66,7 +67,7 @@ abstract contract RuleBasedPrimitive { uint256 entityId, address ruleAddress, bytes32 configSalt, - KeyValue[] calldata ruleParams + KeyValue[] memory ruleParams ) internal { if (entityId == 0) { _emitPrimitiveRuleConfiguredEvent(wasAlreadyConfigured, ruleAddress, configSalt, ruleParams); @@ -92,7 +93,7 @@ abstract contract RuleBasedPrimitive { // Primitive functions: - function _encodePrimitiveConfigureCall(bytes32 configSalt, KeyValue[] calldata ruleParams) + function _encodePrimitiveConfigureCall(bytes32 configSalt, KeyValue[] memory ruleParams) internal pure virtual @@ -102,7 +103,7 @@ abstract contract RuleBasedPrimitive { bool wasAlreadyConfigured, address ruleAddress, bytes32 configSalt, - KeyValue[] calldata ruleParams + KeyValue[] memory ruleParams ) internal virtual; function _emitPrimitiveRuleSelectorEvent( @@ -115,20 +116,19 @@ abstract contract RuleBasedPrimitive { // Entity functions: - function _encodeEntityConfigureCall(uint256 entityId, bytes32 configSalt, KeyValue[] calldata ruleParams) + function _encodeEntityConfigureCall(uint256 entityId, bytes32 configSalt, KeyValue[] memory ruleParams) internal pure virtual - returns (bytes memory) - {} + returns (bytes memory); function _emitEntityRuleConfiguredEvent( bool wasAlreadyConfigured, uint256 entityId, address ruleAddress, bytes32 configSalt, - KeyValue[] calldata ruleParams - ) internal virtual {} + KeyValue[] memory ruleParams + ) internal virtual; function _emitEntityRuleSelectorEvent( bool enabled, @@ -137,42 +137,33 @@ abstract contract RuleBasedPrimitive { bytes32 configSalt, bool isRequired, bytes4 selector - ) internal virtual {} + ) internal virtual; // Internal function _changeRules( RulesStorage storage rulesStorage, uint256 entityId, - RuleChange[] calldata ruleChanges, + RuleChange[] memory ruleChanges, RuleProcessingParams[] memory ruleChangesProcessingParams, - function(uint256,bytes32,KeyValue[] calldata) internal returns (bytes memory) fn_encodeConfigureCall, - function(bool,uint256,address,bytes32,KeyValue[] calldata) internal fn_emitConfiguredEvent, + function(uint256,bytes32,KeyValue[] memory) internal returns (bytes memory) fn_encodeConfigureCall, + function(bool,uint256,address,bytes32,KeyValue[] memory) internal fn_emitConfiguredEvent, function(bool,uint256,address,bytes32,bool,bytes4) internal fn_emitSelectorEvent ) private { _beforeChangeRules(entityId, ruleChanges); for (uint256 i = 0; i < ruleChanges.length; i++) { RuleChange memory ruleChange = ruleChanges[i]; if (ruleChange.configurationChanges.configure) { - ruleChange.configSalt = _configureRule( - rulesStorage, - ruleChanges[i].ruleAddress, - ruleChanges[i].configSalt, - entityId, - ruleChanges[i].configurationChanges.ruleParams, - fn_encodeConfigureCall, - fn_emitConfiguredEvent - ); + ruleChange.configSalt = + _configureRule(rulesStorage, ruleChange, entityId, fn_encodeConfigureCall, fn_emitConfiguredEvent); } for (uint256 j = 0; j < ruleChange.selectorChanges.length; j++) { + _validateIsSupportedRuleSelector( + ruleChange.selectorChanges[j].ruleSelector, + entityId == 0 ? _supportedPrimitiveRuleSelectors() : _supportedEntityRuleSelectors() + ); rulesStorage._changeRulesSelectors( - ruleChanges[i].ruleAddress, - ruleChange.configSalt, - entityId, - ruleChanges[i].selectorChanges[j].ruleSelector, - ruleChanges[i].selectorChanges[j].isRequired, - ruleChanges[i].selectorChanges[j].enabled, - fn_emitSelectorEvent + ruleChange, entityId, ruleChange.selectorChanges[j], fn_emitSelectorEvent ); } } @@ -190,7 +181,19 @@ abstract contract RuleBasedPrimitive { return new bytes4[](0); } - function _beforeChangeRules(uint256 entityId, RuleChange[] calldata ruleChanges) internal virtual { + function _validateIsSupportedRuleSelector(bytes4 ruleSelectorToValidate, bytes4[] memory supportedRuleSelectors) + internal + pure + { + for (uint256 i = 0; i < supportedRuleSelectors.length; i++) { + if (ruleSelectorToValidate == supportedRuleSelectors[i]) { + return; + } + } + revert Errors.UnsupportedSelector(); + } + + function _beforeChangeRules(uint256 entityId, RuleChange[] memory ruleChanges) internal virtual { if (entityId == 0) { _beforeChangePrimitiveRules(ruleChanges); } else { @@ -200,7 +203,7 @@ abstract contract RuleBasedPrimitive { function _processEntityRulesChanges( uint256 entityId, - RuleChange[] calldata ruleChanges, + RuleChange[] memory ruleChanges, RuleProcessingParams[] memory ruleChangesProcessingParams ) internal virtual {} @@ -212,28 +215,51 @@ abstract contract RuleBasedPrimitive { bytes4 ruleSelector = selectorsToValidate[i]; uint256 requiredRulesLength = rulesStorage._getRulesArray(ruleSelector, true).length; uint256 anyOfRulesLength = rulesStorage._getRulesArray(ruleSelector, false).length; - require(anyOfRulesLength != 1, "Cannot have exactly one single any-of rule"); - require(requiredRulesLength + anyOfRulesLength <= RulesLib.MAX_AMOUNT_OF_RULES, "Amount of rules exceeded"); + require(anyOfRulesLength != 1, Errors.SingleAnyOfRule()); + require(requiredRulesLength + anyOfRulesLength <= RulesLib.MAX_AMOUNT_OF_RULES, Errors.LimitReached()); } } - function _beforeChangePrimitiveRules(RuleChange[] calldata ruleChanges) internal virtual {} + function _beforeChangePrimitiveRules(RuleChange[] memory ruleChanges) internal virtual; - function _beforeChangeEntityRules(uint256 entityId, RuleChange[] calldata ruleChanges) internal virtual {} + function _beforeChangeEntityRules(uint256 entityId, RuleChange[] memory ruleChanges) internal virtual; function _configureRule( RulesStorage storage rulesStorage, - address ruleAddress, - bytes32 providedConfigSalt, + RuleChange memory ruleChange, uint256 entityId, - KeyValue[] calldata ruleParams, - function(uint256,bytes32,KeyValue[] calldata) internal returns (bytes memory) fn_encodeConfigureCall, - function(bool,uint256,address,bytes32,KeyValue[] calldata) internal fn_emitEvent + function(uint256,bytes32,KeyValue[] memory) internal returns (bytes memory) fn_encodeConfigureCall, + function(bool,uint256,address,bytes32,KeyValue[] memory) internal fn_emitEvent ) internal returns (bytes32) { - bytes32 configSalt = rulesStorage.generateOrValidateConfigSalt(ruleAddress, providedConfigSalt); - bool wasAlreadyConfigured = - rulesStorage.configureRule(ruleAddress, configSalt, fn_encodeConfigureCall(entityId, configSalt, ruleParams)); - fn_emitEvent(wasAlreadyConfigured, entityId, ruleAddress, configSalt, ruleParams); + bytes32 configSalt = rulesStorage.generateOrValidateConfigSalt(ruleChange.ruleAddress, ruleChange.configSalt); + bool wasAlreadyConfigured = rulesStorage.configureRule( + ruleChange.ruleAddress, + configSalt, + fn_encodeConfigureCall(entityId, configSalt, ruleChange.configurationChanges.ruleParams) + ); + fn_emitEvent( + wasAlreadyConfigured, + entityId, + ruleChange.ruleAddress, + configSalt, + ruleChange.configurationChanges.ruleParams + ); return configSalt; } + + function _getRuleParamsOrEmptyArray(Rule memory rule, RuleProcessingParams[] memory rulesProcessingParams) + internal + pure + returns (KeyValue[] memory) + { + for (uint256 i = 0; i < rulesProcessingParams.length; i++) { + if ( + rulesProcessingParams[i].ruleAddress == rule.ruleAddress + && rulesProcessingParams[i].configSalt == rule.configSalt + ) { + return rulesProcessingParams[i].ruleParams; + } + } + return new KeyValue[](0); + } } diff --git a/contracts/core/base/SourceStampBased.sol b/contracts/core/base/SourceStampBased.sol index 67042153..72d5a2d4 100644 --- a/contracts/core/base/SourceStampBased.sol +++ b/contracts/core/base/SourceStampBased.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {KeyValue, SourceStamp} from "contracts/core/types/Types.sol"; import {ExtraStorageBased} from "contracts/core/base/ExtraStorageBased.sol"; @@ -16,7 +16,7 @@ abstract contract SourceStampBased is ExtraStorageBased { // TODO: We might consider moving source storing out of this contract (see Post created VS lastUpdated source) function _processSourceStamp( uint256 entityId, - KeyValue[] calldata customParams, + KeyValue[] memory customParams, bool storeSource, bool lastUpdatedSourceType ) internal returns (address) { @@ -40,14 +40,14 @@ abstract contract SourceStampBased is ExtraStorageBased { return address(0); } - function _processSourceStamp(uint256 entityId, KeyValue[] calldata customParams, bool storeSource) + function _processSourceStamp(uint256 entityId, KeyValue[] memory customParams, bool storeSource) internal returns (address) { return _processSourceStamp(entityId, customParams, storeSource, false); } - function _processSourceStamp(uint256 entityId, KeyValue[] calldata customParams) internal returns (address) { + function _processSourceStamp(uint256 entityId, KeyValue[] memory customParams) internal returns (address) { return _processSourceStamp(entityId, customParams, true, false); } diff --git a/contracts/core/interfaces/IAccessControl.sol b/contracts/core/interfaces/IAccessControl.sol index 9e2bf965..6c11727e 100644 --- a/contracts/core/interfaces/IAccessControl.sol +++ b/contracts/core/interfaces/IAccessControl.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; interface IAccessControl { /** @@ -12,7 +12,7 @@ interface IAccessControl { * Returns true if the account is allowed to change the access control in the given contract address, false if not. * * @param account The account to check if is allowed to change the access control. - * @param contractAddress The address where the access control . + * @param contractAddress The address where the access control is being changed. */ function canChangeAccessControl(address account, address contractAddress) external view returns (bool); diff --git a/contracts/core/interfaces/IERC4906Events.sol b/contracts/core/interfaces/IERC4906Events.sol index 65bf5d7d..1ab7dff8 100644 --- a/contracts/core/interfaces/IERC4906Events.sol +++ b/contracts/core/interfaces/IERC4906Events.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; library IERC4906Events { /// @dev This event emits when the metadata of a token is changed. diff --git a/contracts/core/interfaces/IERC721.sol b/contracts/core/interfaces/IERC721.sol index 5d0f27e2..ef74a788 100644 --- a/contracts/core/interfaces/IERC721.sol +++ b/contracts/core/interfaces/IERC721.sol @@ -2,7 +2,7 @@ // Copyright (C) 2024 Lens Labs. All Rights Reserved. // Modified from OpenZeppelin's v4.9.0 contracts -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; /** * @dev Required interface of an ERC721 compliant contract. diff --git a/contracts/core/interfaces/IFeed.sol b/contracts/core/interfaces/IFeed.sol index a18d0d6a..f5e4f36a 100644 --- a/contracts/core/interfaces/IFeed.sol +++ b/contracts/core/interfaces/IFeed.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {KeyValue, Rule, RuleProcessingParams, RuleChange} from "contracts/core/types/Types.sol"; import {IMetadataBased} from "contracts/core/interfaces/IMetadataBased.sol"; diff --git a/contracts/core/interfaces/IFeedRule.sol b/contracts/core/interfaces/IFeedRule.sol index e22125b3..a8dc219c 100644 --- a/contracts/core/interfaces/IFeedRule.sol +++ b/contracts/core/interfaces/IFeedRule.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {CreatePostParams, EditPostParams} from "contracts/core/interfaces/IFeed.sol"; import {KeyValue, RuleChange} from "contracts/core/types/Types.sol"; @@ -24,7 +24,7 @@ interface IFeedRule { KeyValue[] calldata ruleParams ) external; - function processRemovePost( + function processDeletePost( bytes32 configSalt, uint256 postId, KeyValue[] calldata primitiveParams, diff --git a/contracts/core/interfaces/IFollowRule.sol b/contracts/core/interfaces/IFollowRule.sol index 70c60c24..499d4583 100644 --- a/contracts/core/interfaces/IFollowRule.sol +++ b/contracts/core/interfaces/IFollowRule.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {KeyValue} from "contracts/core/types/Types.sol"; diff --git a/contracts/core/interfaces/IGraph.sol b/contracts/core/interfaces/IGraph.sol index 509509c4..51628a28 100644 --- a/contracts/core/interfaces/IGraph.sol +++ b/contracts/core/interfaces/IGraph.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {RuleProcessingParams, KeyValue, RuleChange, Rule} from "contracts/core/types/Types.sol"; import {IMetadataBased} from "contracts/core/interfaces/IMetadataBased.sol"; diff --git a/contracts/core/interfaces/IGraphRule.sol b/contracts/core/interfaces/IGraphRule.sol index dbdc7e03..432a69ef 100644 --- a/contracts/core/interfaces/IGraphRule.sol +++ b/contracts/core/interfaces/IGraphRule.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {KeyValue, RuleChange} from "contracts/core/types/Types.sol"; diff --git a/contracts/core/interfaces/IGroup.sol b/contracts/core/interfaces/IGroup.sol index 9a4641e4..9b6e90d3 100644 --- a/contracts/core/interfaces/IGroup.sol +++ b/contracts/core/interfaces/IGroup.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {KeyValue, RuleChange, RuleProcessingParams, Rule} from "contracts/core/types/Types.sol"; import {IMetadataBased} from "contracts/core/interfaces/IMetadataBased.sol"; diff --git a/contracts/core/interfaces/IGroupRule.sol b/contracts/core/interfaces/IGroupRule.sol index e8a41f98..7d54f393 100644 --- a/contracts/core/interfaces/IGroupRule.sol +++ b/contracts/core/interfaces/IGroupRule.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {KeyValue} from "contracts/core/types/Types.sol"; diff --git a/contracts/core/interfaces/ILock.sol b/contracts/core/interfaces/ILock.sol index f572f56c..12141fe6 100644 --- a/contracts/core/interfaces/ILock.sol +++ b/contracts/core/interfaces/ILock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; interface ILock { /** diff --git a/contracts/core/interfaces/IMetadataBased.sol b/contracts/core/interfaces/IMetadataBased.sol index 786d098c..d1c7fa70 100644 --- a/contracts/core/interfaces/IMetadataBased.sol +++ b/contracts/core/interfaces/IMetadataBased.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; interface IMetadataBased { function getMetadataURI() external view returns (string memory); diff --git a/contracts/core/interfaces/INamespace.sol b/contracts/core/interfaces/INamespace.sol index 1b6e7453..3dbc489a 100644 --- a/contracts/core/interfaces/INamespace.sol +++ b/contracts/core/interfaces/INamespace.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {KeyValue, RuleChange, RuleProcessingParams, Rule} from "contracts/core/types/Types.sol"; import {IMetadataBased} from "contracts/core/interfaces/IMetadataBased.sol"; @@ -118,4 +118,10 @@ interface INamespace is IMetadataBased { function getExtraData(bytes32 key) external view returns (bytes memory); function getUsernameExtraData(string calldata username, bytes32 key) external view returns (bytes memory); + + function exists(string calldata username) external view returns (bool); + + function exists(uint256 tokenId) external view returns (bool); + + function getUsernameTokenId(string calldata username) external view returns (uint256); } diff --git a/contracts/core/interfaces/INamespaceRule.sol b/contracts/core/interfaces/INamespaceRule.sol index 55dbd574..cef71656 100644 --- a/contracts/core/interfaces/INamespaceRule.sol +++ b/contracts/core/interfaces/INamespaceRule.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {KeyValue} from "contracts/core/types/Types.sol"; diff --git a/contracts/core/interfaces/IPostRule.sol b/contracts/core/interfaces/IPostRule.sol index 6b4433f3..d7bf9499 100644 --- a/contracts/core/interfaces/IPostRule.sol +++ b/contracts/core/interfaces/IPostRule.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {CreatePostParams, EditPostParams} from "contracts/core/interfaces/IFeed.sol"; import {KeyValue} from "contracts/core/types/Types.sol"; diff --git a/contracts/core/interfaces/IRoleBasedAccessControl.sol b/contracts/core/interfaces/IRoleBasedAccessControl.sol index 673bb092..4202fb80 100644 --- a/contracts/core/interfaces/IRoleBasedAccessControl.sol +++ b/contracts/core/interfaces/IRoleBasedAccessControl.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IAccessControl} from "contracts/core/interfaces/IAccessControl.sol"; diff --git a/contracts/core/interfaces/ISource.sol b/contracts/core/interfaces/ISource.sol index 750957fd..f78bbda5 100644 --- a/contracts/core/interfaces/ISource.sol +++ b/contracts/core/interfaces/ISource.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.17; +pragma solidity ^0.8.26; import {SourceStamp} from "contracts/core/types/Types.sol"; diff --git a/contracts/core/interfaces/ITokenURIProvider.sol b/contracts/core/interfaces/ITokenURIProvider.sol index 5571d9be..ebb77e7f 100644 --- a/contracts/core/interfaces/ITokenURIProvider.sol +++ b/contracts/core/interfaces/ITokenURIProvider.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; interface ITokenURIProvider { function tokenURI(uint256 tokenId) external view returns (string memory); diff --git a/contracts/core/interfaces/IVersionedBeacon.sol b/contracts/core/interfaces/IVersionedBeacon.sol index 8b8fe9cd..9643c331 100644 --- a/contracts/core/interfaces/IVersionedBeacon.sol +++ b/contracts/core/interfaces/IVersionedBeacon.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; interface IVersionedBeacon { /** diff --git a/contracts/core/libraries/AccessControlLib.sol b/contracts/core/libraries/AccessControlLib.sol index e34eb6dd..8a054b22 100644 --- a/contracts/core/libraries/AccessControlLib.sol +++ b/contracts/core/libraries/AccessControlLib.sol @@ -1,8 +1,9 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.17; +pragma solidity ^0.8.26; import {IAccessControl} from "contracts/core/interfaces/IAccessControl.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; library AccessControlLib { function requireAccess(address accessControl, address account, uint256 permissionId) internal view { @@ -10,7 +11,10 @@ library AccessControlLib { } function requireAccess(IAccessControl accessControl, address account, uint256 permissionId) internal view { - require(accessControl.hasAccess({account: account, contractAddress: address(this), permissionId: permissionId})); + require( + accessControl.hasAccess({account: account, contractAddress: address(this), permissionId: permissionId}), + Errors.AccessDenied() + ); } function hasAccess(address accessControl, address account, uint256 permissionId) internal view returns (bool) { @@ -30,7 +34,7 @@ library AccessControlLib { } function verifyHasAccessFunction(IAccessControl accessControl) internal view { - accessControl.hasAccess(address(0), address(0), 0); // We expect this to not panic. + accessControl.hasAccess(address(this), address(this), 1); // We expect this to not panic. } function requireCanChangeAccessControl(address accessControl, address account) internal view { @@ -38,6 +42,9 @@ library AccessControlLib { } function requireCanChangeAccessControl(IAccessControl accessControl, address account) internal view { - require(accessControl.canChangeAccessControl({account: account, contractAddress: address(this)})); + require( + accessControl.canChangeAccessControl({account: account, contractAddress: address(this)}), + Errors.AccessDenied() + ); } } diff --git a/contracts/core/libraries/CallLib.sol b/contracts/core/libraries/CallLib.sol index 8d7df848..a296f782 100644 --- a/contracts/core/libraries/CallLib.sol +++ b/contracts/core/libraries/CallLib.sol @@ -1,21 +1,54 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.17; +pragma solidity ^0.8.26; + +import {Errors} from "contracts/core/types/Errors.sol"; library CallLib { - function safecall(address target, bytes memory data) internal returns (bool, bytes memory) { - (bool success, bytes memory returnData) = target.call(data); - if (success) { - require(returnData.length != 0 || target.code.length != 0, "NOT_A_CONTRACT"); + function safecall(address target, uint256 value, bytes memory data) internal returns (bool, bytes memory) { + (bool callSucceeded, bytes memory returnData) = target.call{value: value}(data); + if (callSucceeded) { + require(returnData.length != 0 || target.code.length != 0, Errors.NotAContract()); } - return (success, returnData); + return (callSucceeded, returnData); } - function safecall(address target, uint256 value, bytes memory data) internal returns (bool, bytes memory) { - (bool success, bytes memory returnData) = target.call{value: value}(data); - if (success) { - require(returnData.length != 0 || target.code.length != 0, "NOT_A_CONTRACT"); + function handledcall(address target, uint256 value, bytes memory data) internal returns (bytes memory) { + (bool callSucceeded, bytes memory returnData) = target.call{value: value}(data); + return _handleCall(callSucceeded, returnData); + } + + function handledsafecall(address target, uint256 value, bytes memory data) internal returns (bytes memory) { + (bool callSucceeded, bytes memory returnData) = safecall(target, value, data); + return _handleCall(callSucceeded, returnData); + } + + function safecall(address target, bytes memory data) internal returns (bool, bytes memory) { + return safecall(target, 0, data); + } + + function handledcall(address target, bytes memory data) internal returns (bytes memory) { + return handledcall(target, 0, data); + } + + function handledsafecall(address target, bytes memory data) internal returns (bytes memory) { + return handledsafecall(target, 0, data); + } + + function _handleCall(bool callSucceeded, bytes memory returnData) private pure returns (bytes memory) { + if (!callSucceeded) { + assembly { + // Get the length of the return data, which contains the error message or selector, as the call failed + let length := mload(returnData) + // If the returned data length is greater than zero... + if iszero(iszero(length)) { + // ...revert with the same error message or selector + revert(add(returnData, 32), length) + } + // else, revert without any error message nor selector + revert(0, 0) + } } - return (success, returnData); + return returnData; } } diff --git a/contracts/core/libraries/EIP712EncodingLib.sol b/contracts/core/libraries/EIP712EncodingLib.sol index 3960b363..56f455a9 100644 --- a/contracts/core/libraries/EIP712EncodingLib.sol +++ b/contracts/core/libraries/EIP712EncodingLib.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.17; +pragma solidity ^0.8.26; import {KeyValue, RuleChange, RuleConfigurationChange, RuleSelectorChange} from "contracts/core/types/Types.sol"; import {CreatePostParams, EditPostParams} from "contracts/core/interfaces/IFeed.sol"; diff --git a/contracts/core/libraries/ExtraDataLib.sol b/contracts/core/libraries/ExtraDataLib.sol index 919ce9b7..629f8d40 100644 --- a/contracts/core/libraries/ExtraDataLib.sol +++ b/contracts/core/libraries/ExtraDataLib.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.17; +pragma solidity ^0.8.26; import {KeyValue} from "contracts/core/types/Types.sol"; diff --git a/contracts/core/libraries/RulesLib.sol b/contracts/core/libraries/RulesLib.sol index e6f96ec4..8774eaf0 100644 --- a/contracts/core/libraries/RulesLib.sol +++ b/contracts/core/libraries/RulesLib.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; -import {Rule} from "contracts/core/types/Types.sol"; +import {Rule, RuleChange, RuleSelectorChange} from "contracts/core/types/Types.sol"; import {CallLib} from "contracts/core/libraries/CallLib.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; struct RulesStorage { mapping(bytes4 => Rule[]) requiredRules; @@ -32,7 +33,7 @@ library RulesLib { if (providedConfigSalt == 0x00) { return bytes32(++rulesStorage.lastConfigSaltGenerated); } else { - require(rulesStorage.isConfigured[ruleAddress][providedConfigSalt]); + require(rulesStorage.isConfigured[ruleAddress][providedConfigSalt], Errors.InvalidConfigSalt()); return providedConfigSalt; } } @@ -45,8 +46,8 @@ library RulesLib { ) internal returns (bool) { bool wasAlreadyConfigured = rulesStorage.isConfigured[ruleAddress][configSalt]; rulesStorage.isConfigured[ruleAddress][configSalt] = true; - (bool success,) = ruleAddress.safecall(encodedConfigureCall); - require(success); + (bool callSucceeded,) = ruleAddress.safecall(encodedConfigureCall); + require(callSucceeded, Errors.ConfigureCallReverted()); return wasAlreadyConfigured; } @@ -57,19 +58,29 @@ library RulesLib { bytes32 configSalt, bytes4 ruleSelector ) internal { - require(rulesStorage.isConfigured[ruleAddress][configSalt]); - require(!_isSelectorAlreadyEnabled(rulesStorage, ruleSelector, ruleAddress, configSalt)); + require(rulesStorage.isConfigured[ruleAddress][configSalt], Errors.RuleNotConfigured()); + if (rulesStorage.ruleStates[ruleSelector][ruleAddress][configSalt].isEnabled) { + if (rulesStorage.ruleStates[ruleSelector][ruleAddress][configSalt].isRequired == isRequired) { + revert Errors.RedundantStateChange(); + } else { + revert Errors.SelectorEnabledForDifferentRuleType(); + } + } _addRuleSelectorToStorage(rulesStorage, ruleSelector, ruleAddress, configSalt, isRequired); } function disableRuleSelector( RulesStorage storage rulesStorage, - bool, /* isRequired */ + bool isRequired, address ruleAddress, bytes32 configSalt, bytes4 ruleSelector ) internal { - require(_isSelectorAlreadyEnabled(rulesStorage, ruleSelector, ruleAddress, configSalt)); + require(rulesStorage.ruleStates[ruleSelector][ruleAddress][configSalt].isEnabled, Errors.RedundantStateChange()); + require( + rulesStorage.ruleStates[ruleSelector][ruleAddress][configSalt].isRequired == isRequired, + Errors.SelectorEnabledForDifferentRuleType() + ); _removeRuleSelectorFromStorage(rulesStorage, ruleSelector, ruleAddress, configSalt); } @@ -83,18 +94,28 @@ library RulesLib { function _changeRulesSelectors( RulesStorage storage rulesStorage, - address ruleAddress, - bytes32 configSalt, + RuleChange memory ruleChange, uint256 entityId, - bytes4 ruleSelector, - bool isRequired, - bool enabled, + RuleSelectorChange memory ruleSelectorChange, function(bool,uint256,address,bytes32,bool,bytes4) internal fn_emitEvent ) internal { function(RulesStorage storage, bool, address, bytes32, bytes4) internal fn_changeRuleSelector = - enabled ? RulesLib.enableRuleSelector : RulesLib.disableRuleSelector; - fn_changeRuleSelector(rulesStorage, isRequired, ruleAddress, configSalt, ruleSelector); - fn_emitEvent(enabled, entityId, ruleAddress, configSalt, isRequired, ruleSelector); + ruleSelectorChange.enabled ? RulesLib.enableRuleSelector : RulesLib.disableRuleSelector; + fn_changeRuleSelector( + rulesStorage, + ruleSelectorChange.isRequired, + ruleChange.ruleAddress, + ruleChange.configSalt, + ruleSelectorChange.ruleSelector + ); + fn_emitEvent( + ruleSelectorChange.enabled, + entityId, + ruleChange.ruleAddress, + ruleChange.configSalt, + ruleSelectorChange.isRequired, + ruleSelectorChange.ruleSelector + ); } // Private @@ -132,13 +153,4 @@ library RulesLib { rules.pop(); delete rulesStorage.ruleStates[ruleSelector][ruleAddress][configSalt]; } - - function _isSelectorAlreadyEnabled( - RulesStorage storage rulesStorage, - bytes4 ruleSelector, - address ruleAddress, - bytes32 configSalt - ) private view returns (bool) { - return rulesStorage.ruleStates[ruleSelector][ruleAddress][configSalt].isEnabled; - } } diff --git a/contracts/core/primitives/feed/Feed.sol b/contracts/core/primitives/feed/Feed.sol index efe50d38..cf2bc520 100644 --- a/contracts/core/primitives/feed/Feed.sol +++ b/contracts/core/primitives/feed/Feed.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IFeed, Post, EditPostParams, CreatePostParams} from "contracts/core/interfaces/IFeed.sol"; import {FeedCore as Core} from "contracts/core/primitives/feed/FeedCore.sol"; @@ -13,6 +13,7 @@ import {Events} from "contracts/core/types/Events.sol"; import {SourceStampBased} from "contracts/core/base/SourceStampBased.sol"; import {MetadataBased} from "contracts/core/base/MetadataBased.sol"; import {Initializable} from "contracts/core/upgradeability/Initializable.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; contract Feed is IFeed, @@ -64,32 +65,33 @@ contract Feed is // Access Controlled functions - function _beforeMetadataURIUpdate(string memory /* metadataURI */ ) internal view override { + function _beforeMetadataURIUpdate(string memory /* metadataURI */ ) internal view virtual override { _requireAccess(msg.sender, PID__SET_METADATA); } - function _beforeChangePrimitiveRules(RuleChange[] calldata /* ruleChanges */ ) internal virtual override { + function _beforeChangePrimitiveRules(RuleChange[] memory /* ruleChanges */ ) internal view virtual override { _requireAccess(msg.sender, PID__CHANGE_RULES); } - function _beforeChangeEntityRules(uint256 entityId, RuleChange[] calldata /* ruleChanges */ ) + function _beforeChangeEntityRules(uint256 entityId, RuleChange[] memory /* ruleChanges */ ) internal + view virtual override { - require(msg.sender == Core.$storage().posts[entityId].author); + require(msg.sender == Core.$storage().posts[entityId].author, Errors.InvalidMsgSender()); } // Public user functions function createPost( - CreatePostParams calldata postParams, - KeyValue[] calldata customParams, - RuleProcessingParams[] calldata feedRulesParams, - RuleProcessingParams[] calldata rootPostRulesParams, - RuleProcessingParams[] calldata quotedPostRulesParams + CreatePostParams memory postParams, + KeyValue[] memory customParams, + RuleProcessingParams[] memory feedRulesParams, + RuleProcessingParams[] memory rootPostRulesParams, + RuleProcessingParams[] memory quotedPostRulesParams ) external virtual override returns (uint256) { - require(msg.sender == postParams.author, "MSG_SENDER_NOT_AUTHOR"); + require(msg.sender == postParams.author, Errors.InvalidMsgSender()); (uint256 postId, uint256 authorPostSequentialId, uint256 rootPostId) = Core._createPost(postParams); address source = _processSourceStamp(postId, customParams); _setPrimitiveInternalExtraDataForEntity(postId, KeyValue(DATA__LAST_UPDATED_SOURCE, abi.encode(source))); @@ -103,7 +105,7 @@ contract Feed is } } if (postId != rootPostId) { - require(postParams.ruleChanges.length == 0, "ONLY_ROOT_POSTS_CAN_HAVE_RULES"); + require(postParams.ruleChanges.length == 0, Errors.CannotHaveRules()); // This covers the Reply or Repost cases _processPostCreationOnRootPost(rootPostId, postId, postParams, customParams, rootPostRulesParams); } else { @@ -133,15 +135,18 @@ contract Feed is function editPost( uint256 postId, EditPostParams calldata postParams, - KeyValue[] calldata customParams, - RuleProcessingParams[] calldata feedRulesParams, - RuleProcessingParams[] calldata rootPostRulesParams, - RuleProcessingParams[] calldata quotedPostRulesParams + KeyValue[] memory customParams, + RuleProcessingParams[] memory feedRulesParams, + RuleProcessingParams[] memory rootPostRulesParams, + RuleProcessingParams[] memory quotedPostRulesParams ) external virtual override { + require(Core._postExists(postId), Errors.DoesNotExist()); address author = Core.$storage().posts[postId].author; // TODO: We can have this for moderators: // require(msg.sender == author || _hasAccess(msg.sender, EDIT_POST_PID)); - require(msg.sender == author, "MSG_SENDER_NOT_AUTHOR"); + require(msg.sender == author, Errors.InvalidMsgSender()); + + Core._editPost(postId, postParams); bool[] memory wereExtraDataValuesSet = new bool[](postParams.extraData.length); for (uint256 i = 0; i < postParams.extraData.length; i++) { @@ -185,10 +190,11 @@ contract Feed is KeyValue[] calldata customParams, RuleProcessingParams[] calldata feedRulesParams ) external virtual override { + require(Core._postExists(postId), Errors.DoesNotExist()); address author = Core.$storage().posts[postId].author; - require(msg.sender == author || _hasAccess(msg.sender, PID__REMOVE_POST), "MSG_SENDER_NOT_AUTHOR_NOR_HAS_ACCESS"); + require(msg.sender == author || _hasAccess(msg.sender, PID__REMOVE_POST), Errors.InvalidMsgSender()); Core._removePost(postId); - _processPostRemoval(postId, customParams, feedRulesParams); + _processPostDeletion(postId, customParams, feedRulesParams); address source = _processSourceStamp(postId, customParams); emit Lens_Feed_PostDeleted(postId, author, customParams, source); } @@ -215,7 +221,7 @@ contract Feed is // Getters function getPost(uint256 postId) external view override returns (Post memory) { - require(Core._postExists(postId), "POST_DOES_NOT_EXIST"); + require(Core._postExists(postId), Errors.DoesNotExist()); return Post({ author: Core.$storage().posts[postId].author, authorPostSequentialId: Core.$storage().posts[postId].authorPostSequentialId, @@ -237,7 +243,7 @@ contract Feed is } function getPostAuthor(uint256 postId) external view override returns (address) { - require(Core._postExists(postId), "POST_DOES_NOT_EXIST"); + require(Core._postExists(postId), Errors.DoesNotExist()); return Core.$storage().posts[postId].author; } @@ -250,7 +256,7 @@ contract Feed is } function getPostExtraData(uint256 postId, bytes32 key) external view override returns (bytes memory) { - require(Core._postExists(postId), "POST_DOES_NOT_EXIST"); + require(Core._postExists(postId), Errors.DoesNotExist()); address postAuthor = Core.$storage().posts[postId].author; return _getEntityExtraData(postAuthor, postId, key); } @@ -260,12 +266,12 @@ contract Feed is } function getPostSequentialId(uint256 postId) external view override returns (uint256) { - require(Core._postExists(postId), "POST_DOES_NOT_EXIST"); + require(Core._postExists(postId), Errors.DoesNotExist()); return Core.$storage().posts[postId].postSequentialId; } function getAuthorPostSequentialId(uint256 postId) external view override returns (uint256) { - require(Core._postExists(postId), "POST_DOES_NOT_EXIST"); + require(Core._postExists(postId), Errors.DoesNotExist()); return Core.$storage().posts[postId].authorPostSequentialId; } diff --git a/contracts/core/primitives/feed/FeedCore.sol b/contracts/core/primitives/feed/FeedCore.sol index 57dbdb82..ac627b01 100644 --- a/contracts/core/primitives/feed/FeedCore.sol +++ b/contracts/core/primitives/feed/FeedCore.sol @@ -1,8 +1,9 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.17; +pragma solidity ^0.8.26; import {EditPostParams, CreatePostParams} from "contracts/core/interfaces/IFeed.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; struct PostStorage { address author; @@ -41,7 +42,7 @@ library FeedCore { return uint256(keccak256(abi.encode("evm:", block.chainid, address(this), author, authorPostSequentialId))); } - function _createPost(CreatePostParams calldata postParams) internal returns (uint256, uint256, uint256) { + function _createPost(CreatePostParams memory postParams) internal returns (uint256, uint256, uint256) { uint256 postSequentialId = ++$storage().postCount; uint256 authorPostSequentialId = ++$storage().authorPostCount[postParams.author]; uint256 postId = _generatePostId(postParams.author, authorPostSequentialId); @@ -52,22 +53,20 @@ library FeedCore { _newPost.contentURI = postParams.contentURI; uint256 rootPostId = postId; if (postParams.quotedPostId != 0) { - require(_postExists(postParams.quotedPostId), "QUOTED_POST_DOES_NOT_EXIST"); + require(_postExists(postParams.quotedPostId), Errors.DoesNotExist()); _newPost.quotedPostId = postParams.quotedPostId; } if (postParams.repliedPostId != 0) { - require(_postExists(postParams.repliedPostId), "REPLIED_POST_DOES_NOT_EXIST"); + require(_postExists(postParams.repliedPostId), Errors.DoesNotExist()); _newPost.repliedPostId = postParams.repliedPostId; rootPostId = $storage().posts[postParams.repliedPostId].rootPostId; } if (postParams.repostedPostId != 0) { - require(_postExists(postParams.repostedPostId), "REPOSTED_POST_DOES_NOT_EXIST"); + require(_postExists(postParams.repostedPostId), Errors.DoesNotExist()); _newPost.repostedPostId = postParams.repostedPostId; rootPostId = $storage().posts[postParams.repostedPostId].rootPostId; - require( - postParams.quotedPostId == 0 && postParams.repliedPostId == 0, "REPOST_CANNOT_HAVE_QUOTED_OR_REPLIED" - ); - require(bytes(postParams.contentURI).length == 0, "REPOST_CANNOT_HAVE_CONTENT"); + require(postParams.quotedPostId == 0 && postParams.repliedPostId == 0, Errors.InvalidParameter()); + require(bytes(postParams.contentURI).length == 0, Errors.InvalidParameter()); } _newPost.rootPostId = rootPostId; _newPost.creationTimestamp = uint80(block.timestamp); @@ -77,9 +76,9 @@ library FeedCore { function _editPost(uint256 postId, EditPostParams calldata postParams) internal { PostStorage storage _post = $storage().posts[postId]; - require(_post.creationTimestamp != 0, "CANNOT_EDIT_NON_EXISTENT_POST"); // Post must exist + require(_post.creationTimestamp != 0, Errors.DoesNotExist()); // Post must exist if (_post.repostedPostId != 0) { - require(bytes(postParams.contentURI).length == 0, "REPOST_CANNOT_HAVE_CONTENT"); + require(bytes(postParams.contentURI).length == 0, Errors.InvalidParameter()); } else { _post.contentURI = postParams.contentURI; } diff --git a/contracts/core/primitives/feed/RuleBasedFeed.sol b/contracts/core/primitives/feed/RuleBasedFeed.sol index e54f1ad4..06f0c3bb 100644 --- a/contracts/core/primitives/feed/RuleBasedFeed.sol +++ b/contracts/core/primitives/feed/RuleBasedFeed.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IPostRule} from "contracts/core/interfaces/IPostRule.sol"; import {IFeedRule} from "contracts/core/interfaces/IFeedRule.sol"; @@ -10,6 +10,7 @@ import {RuleProcessingParams, Rule, RuleChange, KeyValue} from "contracts/core/t import {EditPostParams, CreatePostParams} from "contracts/core/interfaces/IFeed.sol"; import {RuleBasedPrimitive} from "contracts/core/base/RuleBasedPrimitive.sol"; import {CallLib} from "contracts/core/libraries/CallLib.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; abstract contract RuleBasedFeed is IFeed, RuleBasedPrimitive { using RulesLib for RulesStorage; @@ -46,16 +47,24 @@ abstract contract RuleBasedFeed is IFeed, RuleBasedPrimitive { function changePostRules( uint256 postId, RuleChange[] calldata ruleChanges, - RuleProcessingParams[] calldata ruleChangesProcessingParams + RuleProcessingParams[] calldata feedRulesParams ) external virtual override { - _changeEntityRules($postRulesStorage(postId), postId, ruleChanges, ruleChangesProcessingParams); + _changeEntityRules($postRulesStorage(postId), postId, ruleChanges, feedRulesParams); + } + + function _processEntityRulesChanges( + uint256 postId, + RuleChange[] memory ruleChanges, + RuleProcessingParams[] memory feedRulesParams + ) internal virtual override { + _processPostRulesChanges(postId, ruleChanges, feedRulesParams); } function _supportedPrimitiveRuleSelectors() internal view virtual override returns (bytes4[] memory) { bytes4[] memory selectors = new bytes4[](4); selectors[0] = IFeedRule.processCreatePost.selector; selectors[1] = IFeedRule.processEditPost.selector; - selectors[2] = IFeedRule.processRemovePost.selector; + selectors[2] = IFeedRule.processDeletePost.selector; selectors[3] = IFeedRule.processPostRuleChanges.selector; return selectors; } @@ -67,7 +76,7 @@ abstract contract RuleBasedFeed is IFeed, RuleBasedPrimitive { return selectors; } - function _encodePrimitiveConfigureCall(bytes32 configSalt, KeyValue[] calldata ruleParams) + function _encodePrimitiveConfigureCall(bytes32 configSalt, KeyValue[] memory ruleParams) internal pure override @@ -76,11 +85,20 @@ abstract contract RuleBasedFeed is IFeed, RuleBasedPrimitive { return abi.encodeCall(IFeedRule.configure, (configSalt, ruleParams)); } + function _encodeEntityConfigureCall(uint256 postId, bytes32 configSalt, KeyValue[] memory ruleParams) + internal + pure + override + returns (bytes memory) + { + return abi.encodeCall(IPostRule.configure, (configSalt, postId, ruleParams)); + } + function _emitPrimitiveRuleConfiguredEvent( bool wasAlreadyConfigured, address ruleAddress, bytes32 configSalt, - KeyValue[] calldata ruleParams + KeyValue[] memory ruleParams ) internal override { if (wasAlreadyConfigured) { emit IFeed.Lens_Feed_RuleReconfigured(ruleAddress, configSalt, ruleParams); @@ -103,6 +121,39 @@ abstract contract RuleBasedFeed is IFeed, RuleBasedPrimitive { } } + function _emitEntityRuleConfiguredEvent( + bool wasAlreadyConfigured, + uint256 entityId, + address ruleAddress, + bytes32 configSalt, + KeyValue[] memory ruleParams + ) internal override { + if (wasAlreadyConfigured) { + emit IFeed.Lens_Feed_Post_RuleReconfigured(entityId, msg.sender, ruleAddress, configSalt, ruleParams); + } else { + emit IFeed.Lens_Feed_Post_RuleConfigured(entityId, msg.sender, ruleAddress, configSalt, ruleParams); + } + } + + function _emitEntityRuleSelectorEvent( + bool enabled, + uint256 entityId, + address ruleAddress, + bytes32 configSalt, + bool isRequired, + bytes4 selector + ) internal override { + if (enabled) { + emit IFeed.Lens_Feed_Post_RuleSelectorEnabled( + entityId, msg.sender, ruleAddress, configSalt, isRequired, selector + ); + } else { + emit IFeed.Lens_Feed_Post_RuleSelectorDisabled( + entityId, msg.sender, ruleAddress, configSalt, isRequired, selector + ); + } + } + function _amountOfRules(bytes4 ruleSelector) internal view returns (uint256) { return $feedRulesStorage()._getRulesArray(ruleSelector, false).length + $feedRulesStorage()._getRulesArray(ruleSelector, true).length; @@ -126,8 +177,8 @@ abstract contract RuleBasedFeed is IFeed, RuleBasedPrimitive { function _addPostRulesAtCreation( uint256 postId, - CreatePostParams calldata postParams, - RuleProcessingParams[] calldata feedRulesParams + CreatePostParams memory postParams, + RuleProcessingParams[] memory feedRulesParams ) internal { _changeEntityRules($postRulesStorage(postId), postId, postParams.ruleChanges, feedRulesParams); } @@ -135,340 +186,298 @@ abstract contract RuleBasedFeed is IFeed, RuleBasedPrimitive { // Internal function _encodeAndCallProcessCreatePostOnFeed( - address rule, - bytes32 configSalt, - uint256, /* rootPostId */ - uint256 postId, - CreatePostParams calldata postParams, - KeyValue[] calldata primitiveCustomParams, - KeyValue[] memory ruleCustomParams + Rule memory rule, + ProcessPostCreationParams memory processParams, + KeyValue[] memory ruleParams ) internal returns (bool, bytes memory) { - return rule.safecall( + return rule.ruleAddress.safecall( abi.encodeCall( - IFeedRule.processCreatePost, (configSalt, postId, postParams, primitiveCustomParams, ruleCustomParams) + IFeedRule.processCreatePost, + ( + rule.configSalt, + processParams.postId, + processParams.postParams, + processParams.primitiveCustomParams, + ruleParams + ) ) ); } function _encodeAndCallProcessCreatePostOnRootPost( - address rule, - bytes32 configSalt, - uint256 rootPostId, - uint256 postId, - CreatePostParams calldata postParams, - KeyValue[] calldata primitiveCustomParams, - KeyValue[] memory ruleCustomParams + Rule memory rule, + ProcessPostCreationParams memory processParams, + KeyValue[] memory ruleParams ) internal returns (bool, bytes memory) { - return rule.safecall( + return rule.ruleAddress.safecall( abi.encodeCall( IPostRule.processCreatePost, - (configSalt, rootPostId, postId, postParams, primitiveCustomParams, ruleCustomParams) + ( + rule.configSalt, + processParams.rootPostId, + processParams.postId, + processParams.postParams, + processParams.primitiveCustomParams, + ruleParams + ) ) ); } + struct ProcessPostCreationParams { + bytes4 ruleSelector; + uint256 rootPostId; + uint256 postId; + CreatePostParams postParams; + KeyValue[] primitiveCustomParams; + RuleProcessingParams[] rulesProcessingParams; + } + function _processPostCreation( - function(address,bytes32,uint256,uint256,CreatePostParams calldata,KeyValue[] calldata,KeyValue[] memory) internal returns (bool, bytes memory) + function(Rule memory,ProcessPostCreationParams memory,KeyValue[] memory) internal returns (bool, bytes memory) encodeAndCall, - bytes4 ruleSelector, - uint256 rootPostId, - uint256 postId, - CreatePostParams calldata postParams, - KeyValue[] calldata customParams, - RuleProcessingParams[] calldata rulesProcessingParams + ProcessPostCreationParams memory processParams ) internal { - RulesStorage storage _rulesStorage = rootPostId == 0 ? $feedRulesStorage() : $postRulesStorage(rootPostId); + RulesStorage storage _rulesStorage = + processParams.rootPostId == 0 ? $feedRulesStorage() : $postRulesStorage(processParams.rootPostId); + Rule memory rule; + KeyValue[] memory ruleParams; // Check required rules (AND-combined rules) - for (uint256 i = 0; i < _rulesStorage.requiredRules[ruleSelector].length; i++) { - Rule memory rule = _rulesStorage.requiredRules[ruleSelector][i]; - for (uint256 j = 0; j < rulesProcessingParams.length; j++) { - KeyValue[] memory ruleCustomParams = new KeyValue[](0); - if ( - rulesProcessingParams[j].ruleAddress == rule.ruleAddress - && rulesProcessingParams[j].configSalt == rule.configSalt - ) { - ruleCustomParams = rulesProcessingParams[j].ruleParams; - } - (bool callNotReverted,) = encodeAndCall( - rule.ruleAddress, rule.configSalt, rootPostId, postId, postParams, customParams, ruleCustomParams - ); - require(callNotReverted, "Some required rule failed"); - } + for (uint256 i = 0; i < _rulesStorage.requiredRules[processParams.ruleSelector].length; i++) { + rule = _rulesStorage.requiredRules[processParams.ruleSelector][i]; + ruleParams = _getRuleParamsOrEmptyArray(rule, processParams.rulesProcessingParams); + (bool callSucceeded,) = encodeAndCall(rule, processParams, ruleParams); + require(callSucceeded, Errors.RequiredRuleReverted()); } // Check any-of rules (OR-combined rules) - for (uint256 i = 0; i < _rulesStorage.anyOfRules[ruleSelector].length; i++) { - Rule memory rule = _rulesStorage.anyOfRules[ruleSelector][i]; - for (uint256 j = 0; j < rulesProcessingParams.length; j++) { - KeyValue[] memory ruleCustomParams = new KeyValue[](0); - if ( - rulesProcessingParams[j].ruleAddress == rule.ruleAddress - && rulesProcessingParams[j].configSalt == rule.configSalt - ) { - ruleCustomParams = rulesProcessingParams[j].ruleParams; - } - (bool callNotReverted,) = encodeAndCall( - rule.ruleAddress, rule.configSalt, rootPostId, postId, postParams, customParams, ruleCustomParams - ); - if (callNotReverted) { - return; // If any of the OR-combined rules passed, it means they succeed and we can return - } + for (uint256 i = 0; i < _rulesStorage.anyOfRules[processParams.ruleSelector].length; i++) { + rule = _rulesStorage.anyOfRules[processParams.ruleSelector][i]; + ruleParams = _getRuleParamsOrEmptyArray(rule, processParams.rulesProcessingParams); + (bool callSucceeded,) = encodeAndCall(rule, processParams, ruleParams); + if (callSucceeded) { + return; // If any of the OR-combined rules passed, it means they succeed and we can return } } // If there are any-of rules and it reached this point, it means all of them failed. - require(_rulesStorage.anyOfRules[ruleSelector].length == 0, "All of the any-of rules failed"); + require(_rulesStorage.anyOfRules[processParams.ruleSelector].length == 0, Errors.AllAnyOfRulesReverted()); } function _processPostCreationOnRootPost( uint256 rootPostId, uint256 postId, - CreatePostParams calldata postParams, - KeyValue[] calldata customParams, - RuleProcessingParams[] calldata postRulesParams + CreatePostParams memory postParams, + KeyValue[] memory primitiveCustomParams, + RuleProcessingParams[] memory postRulesParams ) internal { _processPostCreation( _encodeAndCallProcessCreatePostOnRootPost, - IPostRule.processCreatePost.selector, - rootPostId, - postId, - postParams, - customParams, - postRulesParams + ProcessPostCreationParams({ + ruleSelector: IPostRule.processCreatePost.selector, + rootPostId: rootPostId, + postId: postId, + postParams: postParams, + primitiveCustomParams: primitiveCustomParams, + rulesProcessingParams: postRulesParams + }) ); } function _processPostCreationOnFeed( uint256 postId, - CreatePostParams calldata postParams, - KeyValue[] calldata customParams, - RuleProcessingParams[] calldata feedRulesParams + CreatePostParams memory postParams, + KeyValue[] memory primitiveCustomParams, + RuleProcessingParams[] memory feedRulesParams ) internal { _processPostCreation( _encodeAndCallProcessCreatePostOnFeed, - IFeedRule.processCreatePost.selector, - 0, - postId, - postParams, - customParams, - feedRulesParams + ProcessPostCreationParams({ + ruleSelector: IFeedRule.processCreatePost.selector, + rootPostId: 0, + postId: postId, + postParams: postParams, + primitiveCustomParams: primitiveCustomParams, + rulesProcessingParams: feedRulesParams + }) ); } function _processPostEditingOnRootPost( uint256 rootPostId, uint256 postId, - EditPostParams calldata postParams, - KeyValue[] calldata customParams, - RuleProcessingParams[] calldata postRulesParams + EditPostParams memory postParams, + KeyValue[] memory primitiveCustomParams, + RuleProcessingParams[] memory postRulesParams ) internal { _processPostEditing( _encodeAndCallProcessEditPostOnRootPost, - IPostRule.processEditPost.selector, - rootPostId, - postId, - postParams, - customParams, - postRulesParams + ProcessPostEditingParams({ + ruleSelector: IPostRule.processEditPost.selector, + rootPostId: rootPostId, + postId: postId, + postParams: postParams, + primitiveCustomParams: primitiveCustomParams, + rulesProcessingParams: postRulesParams + }) ); } function _processPostEditingOnFeed( uint256 postId, - EditPostParams calldata postParams, - KeyValue[] calldata customParams, - RuleProcessingParams[] calldata feedRulesParams + EditPostParams memory postParams, + KeyValue[] memory primitiveCustomParams, + RuleProcessingParams[] memory feedRulesParams ) internal { _processPostEditing( _encodeAndCallProcessEditPostOnFeed, - IFeedRule.processEditPost.selector, - 0, - postId, - postParams, - customParams, - feedRulesParams + ProcessPostEditingParams({ + ruleSelector: IFeedRule.processEditPost.selector, + rootPostId: 0, + postId: postId, + postParams: postParams, + primitiveCustomParams: primitiveCustomParams, + rulesProcessingParams: feedRulesParams + }) ); } function _encodeAndCallProcessEditPostOnFeed( - address rule, - bytes32 configSalt, - uint256, /* rootPostId */ - uint256 postId, - EditPostParams calldata postParams, - KeyValue[] calldata primitiveCustomParams, - KeyValue[] memory ruleCustomParams + Rule memory rule, + ProcessPostEditingParams memory processParams, + KeyValue[] memory ruleParams ) internal returns (bool, bytes memory) { - return rule.safecall( + return rule.ruleAddress.safecall( abi.encodeCall( - IFeedRule.processEditPost, (configSalt, postId, postParams, primitiveCustomParams, ruleCustomParams) + IFeedRule.processEditPost, + ( + rule.configSalt, + processParams.postId, + processParams.postParams, + processParams.primitiveCustomParams, + ruleParams + ) ) ); } function _encodeAndCallProcessEditPostOnRootPost( - address rule, - bytes32 configSalt, - uint256 rootPostId, - uint256 postId, - EditPostParams calldata postParams, - KeyValue[] calldata primitiveCustomParams, - KeyValue[] memory ruleCustomParams + Rule memory rule, + ProcessPostEditingParams memory processParams, + KeyValue[] memory ruleParams ) internal returns (bool, bytes memory) { - return rule.safecall( + return rule.ruleAddress.safecall( abi.encodeCall( IPostRule.processEditPost, - (configSalt, rootPostId, postId, postParams, primitiveCustomParams, ruleCustomParams) + ( + rule.configSalt, + processParams.rootPostId, + processParams.postId, + processParams.postParams, + processParams.primitiveCustomParams, + ruleParams + ) ) ); } + struct ProcessPostEditingParams { + bytes4 ruleSelector; + uint256 rootPostId; + uint256 postId; + EditPostParams postParams; + KeyValue[] primitiveCustomParams; + RuleProcessingParams[] rulesProcessingParams; + } + function _processPostEditing( - function(address,bytes32,uint256,uint256,EditPostParams calldata,KeyValue[] calldata,KeyValue[] memory) internal returns (bool, bytes memory) + function(Rule memory,ProcessPostEditingParams memory,KeyValue[] memory) internal returns (bool,bytes memory) encodeAndCall, - bytes4 ruleSelector, - uint256 rootPostId, - uint256 postId, - EditPostParams calldata postParams, - KeyValue[] calldata customParams, - RuleProcessingParams[] calldata rulesProcessingParams + ProcessPostEditingParams memory processParams ) internal { - RulesStorage storage _rulesStorage = rootPostId == 0 ? $feedRulesStorage() : $postRulesStorage(rootPostId); + RulesStorage storage _rulesStorage = + processParams.rootPostId == 0 ? $feedRulesStorage() : $postRulesStorage(processParams.rootPostId); + Rule memory rule; + KeyValue[] memory ruleParams; // Check required rules (AND-combined rules) - for (uint256 i = 0; i < _rulesStorage.requiredRules[ruleSelector].length; i++) { - Rule memory rule = _rulesStorage.requiredRules[ruleSelector][i]; - for (uint256 j = 0; j < rulesProcessingParams.length; j++) { - KeyValue[] memory ruleCustomParams = new KeyValue[](0); - if ( - rulesProcessingParams[j].ruleAddress == rule.ruleAddress - && rulesProcessingParams[j].configSalt == rule.configSalt - ) { - ruleCustomParams = rulesProcessingParams[j].ruleParams; - } - (bool callNotReverted,) = encodeAndCall( - rule.ruleAddress, rule.configSalt, rootPostId, postId, postParams, customParams, ruleCustomParams - ); - require(callNotReverted, "Some required rule failed"); - } + for (uint256 i = 0; i < _rulesStorage.requiredRules[processParams.ruleSelector].length; i++) { + rule = _rulesStorage.requiredRules[processParams.ruleSelector][i]; + ruleParams = _getRuleParamsOrEmptyArray(rule, processParams.rulesProcessingParams); + (bool callSucceeded,) = encodeAndCall(rule, processParams, ruleParams); + require(callSucceeded, Errors.RequiredRuleReverted()); } // Check any-of rules (OR-combined rules) - for (uint256 i = 0; i < _rulesStorage.anyOfRules[ruleSelector].length; i++) { - Rule memory rule = _rulesStorage.anyOfRules[ruleSelector][i]; - for (uint256 j = 0; j < rulesProcessingParams.length; j++) { - KeyValue[] memory ruleCustomParams = new KeyValue[](0); - if ( - rulesProcessingParams[j].ruleAddress == rule.ruleAddress - && rulesProcessingParams[j].configSalt == rule.configSalt - ) { - ruleCustomParams = rulesProcessingParams[j].ruleParams; - } - (bool callNotReverted,) = encodeAndCall( - rule.ruleAddress, rule.configSalt, rootPostId, postId, postParams, customParams, ruleCustomParams - ); - if (callNotReverted) { - return; // If any of the OR-combined rules passed, it means they succeed and we can return - } + for (uint256 i = 0; i < _rulesStorage.anyOfRules[processParams.ruleSelector].length; i++) { + rule = _rulesStorage.anyOfRules[processParams.ruleSelector][i]; + ruleParams = _getRuleParamsOrEmptyArray(rule, processParams.rulesProcessingParams); + (bool callSucceeded,) = encodeAndCall(rule, processParams, ruleParams); + if (callSucceeded) { + return; // If any of the OR-combined rules passed, it means they succeed and we can return } } // If there are any-of rules and it reached this point, it means all of them failed. - require(_rulesStorage.anyOfRules[ruleSelector].length == 0, "All of the any-of rules failed"); + require(_rulesStorage.anyOfRules[processParams.ruleSelector].length == 0, Errors.AllAnyOfRulesReverted()); } - function _processPostRemoval( + function _processPostDeletion( uint256 postId, KeyValue[] calldata customParams, RuleProcessingParams[] calldata rulesProcessingParams ) internal { - bytes4 ruleSelector = IFeedRule.processRemovePost.selector; + bytes4 ruleSelector = IFeedRule.processDeletePost.selector; + Rule memory rule; + KeyValue[] memory ruleParams; // Check required rules (AND-combined rules) for (uint256 i = 0; i < $feedRulesStorage().requiredRules[ruleSelector].length; i++) { - Rule memory rule = $feedRulesStorage().requiredRules[ruleSelector][i]; - for (uint256 j = 0; j < rulesProcessingParams.length; j++) { - KeyValue[] memory ruleCustomParams = new KeyValue[](0); - if ( - rulesProcessingParams[j].ruleAddress == rule.ruleAddress - && rulesProcessingParams[j].configSalt == rule.configSalt - ) { - ruleCustomParams = rulesProcessingParams[j].ruleParams; - } - (bool callNotReverted,) = rule.ruleAddress.safecall( - abi.encodeCall( - IFeedRule.processRemovePost, (rule.configSalt, postId, customParams, ruleCustomParams) - ) - ); - require(callNotReverted, "Some required rule failed"); - } + rule = $feedRulesStorage().requiredRules[ruleSelector][i]; + ruleParams = _getRuleParamsOrEmptyArray(rule, rulesProcessingParams); + (bool callSucceeded,) = rule.ruleAddress.safecall( + abi.encodeCall(IFeedRule.processDeletePost, (rule.configSalt, postId, customParams, ruleParams)) + ); + require(callSucceeded, Errors.RequiredRuleReverted()); } // Check any-of rules (OR-combined rules) for (uint256 i = 0; i < $feedRulesStorage().anyOfRules[ruleSelector].length; i++) { - Rule memory rule = $feedRulesStorage().anyOfRules[ruleSelector][i]; - for (uint256 j = 0; j < rulesProcessingParams.length; j++) { - KeyValue[] memory ruleCustomParams = new KeyValue[](0); - if ( - rulesProcessingParams[j].ruleAddress == rule.ruleAddress - && rulesProcessingParams[j].configSalt == rule.configSalt - ) { - ruleCustomParams = rulesProcessingParams[j].ruleParams; - } - (bool callNotReverted,) = rule.ruleAddress.safecall( - abi.encodeCall( - IFeedRule.processRemovePost, (rule.configSalt, postId, customParams, ruleCustomParams) - ) - ); - if (callNotReverted) { - return; // If any of the OR-combined rules passed, it means they succeed and we can return - } + rule = $feedRulesStorage().anyOfRules[ruleSelector][i]; + ruleParams = _getRuleParamsOrEmptyArray(rule, rulesProcessingParams); + (bool callSucceeded,) = rule.ruleAddress.safecall( + abi.encodeCall(IFeedRule.processDeletePost, (rule.configSalt, postId, customParams, ruleParams)) + ); + if (callSucceeded) { + return; // If any of the OR-combined rules passed, it means they succeed and we can return } } // If there are any-of rules and it reached this point, it means all of them failed. - require($feedRulesStorage().anyOfRules[ruleSelector].length == 0, "All of the any-of rules failed"); + require($feedRulesStorage().anyOfRules[ruleSelector].length == 0, Errors.AllAnyOfRulesReverted()); } function _processPostRulesChanges( uint256 postId, - RuleChange[] calldata ruleChanges, - RuleProcessingParams[] calldata rulesProcessingParams + RuleChange[] memory ruleChanges, + RuleProcessingParams[] memory rulesProcessingParams ) internal { bytes4 ruleSelector = IFeedRule.processPostRuleChanges.selector; + Rule memory rule; + KeyValue[] memory ruleParams; // Check required rules (AND-combined rules) for (uint256 i = 0; i < $feedRulesStorage().requiredRules[ruleSelector].length; i++) { - Rule memory rule = $feedRulesStorage().requiredRules[ruleSelector][i]; - for (uint256 j = 0; j < rulesProcessingParams.length; j++) { - KeyValue[] memory ruleCustomParams = new KeyValue[](0); - if ( - rulesProcessingParams[j].ruleAddress == rule.ruleAddress - && rulesProcessingParams[j].configSalt == rule.configSalt - ) { - ruleCustomParams = rulesProcessingParams[j].ruleParams; - } - (bool callNotReverted,) = rule.ruleAddress.safecall( - abi.encodeCall( - IFeedRule.processPostRuleChanges, (rule.configSalt, postId, ruleChanges, ruleCustomParams) - ) - ); - require(callNotReverted, "Some required rule failed"); - } + rule = $feedRulesStorage().requiredRules[ruleSelector][i]; + ruleParams = _getRuleParamsOrEmptyArray(rule, rulesProcessingParams); + (bool callSucceeded,) = rule.ruleAddress.safecall( + abi.encodeCall(IFeedRule.processPostRuleChanges, (rule.configSalt, postId, ruleChanges, ruleParams)) + ); + require(callSucceeded, Errors.RequiredRuleReverted()); } // Check any-of rules (OR-combined rules) for (uint256 i = 0; i < $feedRulesStorage().anyOfRules[ruleSelector].length; i++) { - Rule memory rule = $feedRulesStorage().anyOfRules[ruleSelector][i]; - for (uint256 j = 0; j < rulesProcessingParams.length; j++) { - KeyValue[] memory ruleCustomParams = new KeyValue[](0); - if ( - rulesProcessingParams[j].ruleAddress == rule.ruleAddress - && rulesProcessingParams[j].configSalt == rule.configSalt - ) { - ruleCustomParams = rulesProcessingParams[j].ruleParams; - } - (bool callNotReverted,) = rule.ruleAddress.safecall( - abi.encodeCall( - IFeedRule.processPostRuleChanges, (rule.configSalt, postId, ruleChanges, ruleCustomParams) - ) - ); - if (callNotReverted) { - return; // If any of the OR-combined rules passed, it means they succeed and we can return - } + rule = $feedRulesStorage().anyOfRules[ruleSelector][i]; + ruleParams = _getRuleParamsOrEmptyArray(rule, rulesProcessingParams); + (bool callSucceeded,) = rule.ruleAddress.safecall( + abi.encodeCall(IFeedRule.processPostRuleChanges, (rule.configSalt, postId, ruleChanges, ruleParams)) + ); + if (callSucceeded) { + return; // If any of the OR-combined rules passed, it means they succeed and we can return } } // If there are any-of rules and it reached this point, it means all of them failed. - require($feedRulesStorage().anyOfRules[ruleSelector].length == 0, "All of the any-of rules failed"); + require($feedRulesStorage().anyOfRules[ruleSelector].length == 0, Errors.AllAnyOfRulesReverted()); } } diff --git a/contracts/core/primitives/graph/Graph.sol b/contracts/core/primitives/graph/Graph.sol index 7284b482..4ed4b3b2 100644 --- a/contracts/core/primitives/graph/Graph.sol +++ b/contracts/core/primitives/graph/Graph.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {Follow, IGraph} from "contracts/core/interfaces/IGraph.sol"; import {GraphCore as Core} from "contracts/core/primitives/graph/GraphCore.sol"; @@ -13,6 +13,7 @@ import {Events} from "contracts/core/types/Events.sol"; import {SourceStampBased} from "contracts/core/base/SourceStampBased.sol"; import {MetadataBased} from "contracts/core/base/MetadataBased.sol"; import {Initializable} from "contracts/core/upgradeability/Initializable.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; contract Graph is IGraph, @@ -64,16 +65,16 @@ contract Graph is _requireAccess(msg.sender, PID__SET_METADATA); } - function _beforeChangePrimitiveRules(RuleChange[] calldata /* ruleChanges */ ) internal virtual override { + function _beforeChangePrimitiveRules(RuleChange[] memory /* ruleChanges */ ) internal virtual override { _requireAccess(msg.sender, PID__CHANGE_RULES); } - function _beforeChangeEntityRules(uint256 entityId, RuleChange[] calldata /* ruleChanges */ ) + function _beforeChangeEntityRules(uint256 entityId, RuleChange[] memory /* ruleChanges */ ) internal virtual override { - require(msg.sender == address(uint160(entityId))); // Follow rules can only be changed in your own account + require(msg.sender == address(uint160(entityId)), Errors.InvalidMsgSender()); // Follow rules can only be changed in your own account } function setExtraData(KeyValue[] calldata extraDataToSet) external override { @@ -105,7 +106,7 @@ contract Graph is RuleProcessingParams[] calldata followRulesProcessingParams, KeyValue[] calldata extraData ) external virtual override returns (uint256) { - require(msg.sender == followerAccount); + require(msg.sender == followerAccount, Errors.InvalidMsgSender()); // followId is now in customParams - think if we want to implement this now, or later. For now passing 0 always. uint256 assignedFollowId = Core._follow(followerAccount, accountToFollow, 0, block.timestamp); address source = _processSourceStamp(assignedFollowId, customParams); @@ -130,7 +131,7 @@ contract Graph is KeyValue[] calldata customParams, RuleProcessingParams[] calldata graphRulesProcessingParams ) external virtual override returns (uint256) { - require(msg.sender == followerAccount); + require(msg.sender == followerAccount, Errors.InvalidMsgSender()); uint256 followId = Core._unfollow(followerAccount, accountToUnfollow); address source = _processSourceStamp(followId, customParams); _graphProcessUnfollow(msg.sender, followerAccount, accountToUnfollow, customParams, graphRulesProcessingParams); @@ -148,13 +149,13 @@ contract Graph is function getFollowerById(address account, uint256 followId) external view override returns (address) { address follower = Core.$storage().followers[account][followId]; - require(follower != address(0), "FOLLOWER_DOES_NOT_EXIST"); + require(follower != address(0), Errors.DoesNotExist()); return follower; } function getFollow(address followerAccount, address targetAccount) external view override returns (Follow memory) { Follow memory followData = Core.$storage().follows[followerAccount][targetAccount]; - require(followData.id != 0, "FOLLOW_DOES_NOT_EXIST"); + require(followData.id != 0, Errors.DoesNotExist()); return followData; } diff --git a/contracts/core/primitives/graph/GraphCore.sol b/contracts/core/primitives/graph/GraphCore.sol index af3b9a44..104e165c 100644 --- a/contracts/core/primitives/graph/GraphCore.sol +++ b/contracts/core/primitives/graph/GraphCore.sol @@ -1,8 +1,9 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.17; +pragma solidity ^0.8.26; import {Follow} from "contracts/core/interfaces/IGraph.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; library GraphCore { // Storage @@ -30,13 +31,15 @@ library GraphCore { internal returns (uint256) { - require(followerAccount != accountToFollow); // Cannot follow yourself - require($storage().follows[followerAccount][accountToFollow].id == 0); // Cannot follow more than once + require(followerAccount != address(0), Errors.InvalidParameter()); + require(accountToFollow != address(0), Errors.InvalidParameter()); + require(followerAccount != accountToFollow, Errors.ActionOnSelf()); + require($storage().follows[followerAccount][accountToFollow].id == 0, Errors.CannotFollowAgain()); if (followId == 0) { followId = ++$storage().lastFollowIdAssigned[accountToFollow]; } else { - require(followId < $storage().lastFollowIdAssigned[accountToFollow]); // Only previous Follow IDs allowed to be reused - require($storage().followers[accountToFollow][followId] == address(0)); // Follow ID is already taken + require(followId < $storage().lastFollowIdAssigned[accountToFollow], Errors.InvalidParameter()); // Only previous Follow IDs allowed to be reused + require($storage().followers[accountToFollow][followId] == address(0), Errors.AlreadyExists()); // Follow ID is already taken } $storage().follows[followerAccount][accountToFollow] = Follow({id: followId, timestamp: timestamp}); $storage().followers[accountToFollow][followId] = followerAccount; @@ -46,8 +49,10 @@ library GraphCore { } function _unfollow(address followerAccount, address accountToUnfollow) internal returns (uint256) { + require(followerAccount != address(0), Errors.InvalidParameter()); + require(accountToUnfollow != address(0), Errors.InvalidParameter()); uint256 followId = $storage().follows[followerAccount][accountToUnfollow].id; - require(followId != 0); // Must be following + require(followId != 0, Errors.NotFollowing()); // Must be following $storage().followersCount[accountToUnfollow]--; $storage().followingCount[followerAccount]--; delete $storage().followers[accountToUnfollow][followId]; diff --git a/contracts/core/primitives/graph/RuleBasedGraph.sol b/contracts/core/primitives/graph/RuleBasedGraph.sol index 7f7ae841..5279b736 100644 --- a/contracts/core/primitives/graph/RuleBasedGraph.sol +++ b/contracts/core/primitives/graph/RuleBasedGraph.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IFollowRule} from "contracts/core/interfaces/IFollowRule.sol"; import {IGraphRule} from "contracts/core/interfaces/IGraphRule.sol"; @@ -9,6 +9,7 @@ import {RuleProcessingParams, RuleChange, Rule, KeyValue} from "contracts/core/t import {IGraph} from "contracts/core/interfaces/IGraph.sol"; import {RuleBasedPrimitive} from "contracts/core/base/RuleBasedPrimitive.sol"; import {CallLib} from "contracts/core/libraries/CallLib.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; abstract contract RuleBasedGraph is IGraph, RuleBasedPrimitive { using RulesLib for RulesStorage; @@ -52,6 +53,14 @@ abstract contract RuleBasedGraph is IGraph, RuleBasedPrimitive { ); } + function _processEntityRulesChanges( + uint256 entityId, + RuleChange[] memory ruleChanges, + RuleProcessingParams[] memory ruleChangesProcessingParams + ) internal virtual override { + _graphProcessFollowRuleChanges(address(uint160(entityId)), ruleChanges, ruleChangesProcessingParams); + } + function _supportedPrimitiveRuleSelectors() internal view virtual override returns (bytes4[] memory) { bytes4[] memory selectors = new bytes4[](3); selectors[0] = IGraphRule.processFollow.selector; @@ -66,7 +75,7 @@ abstract contract RuleBasedGraph is IGraph, RuleBasedPrimitive { return selectors; } - function _encodePrimitiveConfigureCall(bytes32 configSalt, KeyValue[] calldata ruleParams) + function _encodePrimitiveConfigureCall(bytes32 configSalt, KeyValue[] memory ruleParams) internal pure override @@ -75,11 +84,20 @@ abstract contract RuleBasedGraph is IGraph, RuleBasedPrimitive { return abi.encodeCall(IGraphRule.configure, (configSalt, ruleParams)); } + function _encodeEntityConfigureCall(uint256 accountAsUint256, bytes32 configSalt, KeyValue[] memory ruleParams) + internal + pure + override + returns (bytes memory) + { + return abi.encodeCall(IFollowRule.configure, (configSalt, address(uint160(accountAsUint256)), ruleParams)); + } + function _emitPrimitiveRuleConfiguredEvent( bool wasAlreadyConfigured, address ruleAddress, bytes32 configSalt, - KeyValue[] calldata ruleParams + KeyValue[] memory ruleParams ) internal override { if (wasAlreadyConfigured) { emit IGraph.Lens_Graph_RuleReconfigured(ruleAddress, configSalt, ruleParams); @@ -102,6 +120,37 @@ abstract contract RuleBasedGraph is IGraph, RuleBasedPrimitive { } } + function _emitEntityRuleConfiguredEvent( + bool wasAlreadyConfigured, + uint256 entityId, + address ruleAddress, + bytes32 configSalt, + KeyValue[] memory ruleParams + ) internal override { + address account = address(uint160(entityId)); + if (wasAlreadyConfigured) { + emit IGraph.Lens_Graph_Follow_RuleReconfigured(account, ruleAddress, configSalt, ruleParams); + } else { + emit IGraph.Lens_Graph_Follow_RuleConfigured(account, ruleAddress, configSalt, ruleParams); + } + } + + function _emitEntityRuleSelectorEvent( + bool enabled, + uint256 entityId, + address ruleAddress, + bytes32 configSalt, + bool isRequired, + bytes4 selector + ) internal override { + address account = address(uint160(entityId)); + if (enabled) { + emit IGraph.Lens_Graph_Follow_RuleSelectorEnabled(account, ruleAddress, configSalt, isRequired, selector); + } else { + emit IGraph.Lens_Graph_Follow_RuleSelectorDisabled(account, ruleAddress, configSalt, isRequired, selector); + } + } + function _amountOfRules(bytes4 ruleSelector) internal view returns (uint256) { return $graphRulesStorage()._getRulesArray(ruleSelector, false).length + $graphRulesStorage()._getRulesArray(ruleSelector, true).length; @@ -131,73 +180,59 @@ abstract contract RuleBasedGraph is IGraph, RuleBasedPrimitive { function _graphProcessFollowRuleChanges( address account, - RuleChange[] calldata ruleChanges, - RuleProcessingParams[] calldata graphRulesProcessingParams + RuleChange[] memory ruleChanges, + RuleProcessingParams[] memory graphRulesProcessingParams ) internal { bytes4 ruleSelector = IGraphRule.processFollowRuleChanges.selector; + Rule memory rule; + KeyValue[] memory ruleParams; // Check required rules (AND-combined rules) for (uint256 i = 0; i < $graphRulesStorage().requiredRules[ruleSelector].length; i++) { - Rule memory rule = $graphRulesStorage().requiredRules[ruleSelector][i]; - for (uint256 j = 0; j < graphRulesProcessingParams.length; j++) { - KeyValue[] memory ruleCustomParams = new KeyValue[](0); - if ( - graphRulesProcessingParams[j].ruleAddress == rule.ruleAddress - && graphRulesProcessingParams[j].configSalt == rule.configSalt - ) { - ruleCustomParams = graphRulesProcessingParams[j].ruleParams; - } - (bool callNotReverted,) = rule.ruleAddress.safecall( - abi.encodeCall( - IGraphRule.processFollowRuleChanges, (rule.configSalt, account, ruleChanges, ruleCustomParams) - ) - ); - require(callNotReverted, "Some required rule failed"); - } + rule = $graphRulesStorage().requiredRules[ruleSelector][i]; + ruleParams = _getRuleParamsOrEmptyArray(rule, graphRulesProcessingParams); + (bool callSucceeded,) = rule.ruleAddress.safecall( + abi.encodeCall(IGraphRule.processFollowRuleChanges, (rule.configSalt, account, ruleChanges, ruleParams)) + ); + require(callSucceeded, Errors.RequiredRuleReverted()); } // Check any-of rules (OR-combined rules) for (uint256 i = 0; i < $graphRulesStorage().anyOfRules[ruleSelector].length; i++) { - Rule memory rule = $graphRulesStorage().anyOfRules[ruleSelector][i]; - for (uint256 j = 0; j < graphRulesProcessingParams.length; j++) { - KeyValue[] memory ruleCustomParams = new KeyValue[](0); - if ( - graphRulesProcessingParams[j].ruleAddress == rule.ruleAddress - && graphRulesProcessingParams[j].configSalt == rule.configSalt - ) { - ruleCustomParams = graphRulesProcessingParams[j].ruleParams; - } - (bool callNotReverted,) = rule.ruleAddress.safecall( - abi.encodeCall( - IGraphRule.processFollowRuleChanges, (rule.configSalt, account, ruleChanges, ruleCustomParams) - ) - ); - if (callNotReverted) { - return; // If any of the OR-combined rules passed, it means they succeed and we can return - } + rule = $graphRulesStorage().anyOfRules[ruleSelector][i]; + ruleParams = _getRuleParamsOrEmptyArray(rule, graphRulesProcessingParams); + (bool callSucceeded,) = rule.ruleAddress.safecall( + abi.encodeCall(IGraphRule.processFollowRuleChanges, (rule.configSalt, account, ruleChanges, ruleParams)) + ); + if (callSucceeded) { + return; // If any of the OR-combined rules passed, it means they succeed and we can return } } // If there are any-of rules and it reached this point, it means all of them failed. - require($graphRulesStorage().anyOfRules[ruleSelector].length == 0, "All of the any-of rules failed"); + require($graphRulesStorage().anyOfRules[ruleSelector].length == 0, Errors.AllAnyOfRulesReverted()); + } + + struct ProcessParams { + address originalMsgSender; + address sourceAccount; + address targetAccount; + KeyValue[] primitiveCustomParams; + RuleProcessingParams[] rulesProcessingParams; } function _encodeAndCallGraphProcessFollow( - address rule, - bytes32 configSalt, - address originalMsgSender, - address followerAccount, - address accountToFollow, - KeyValue[] calldata primitiveCustomParams, - KeyValue[] memory ruleCustomParams + Rule memory rule, + ProcessParams memory processParams, + KeyValue[] memory ruleParams ) internal returns (bool, bytes memory) { - return rule.safecall( + return rule.ruleAddress.safecall( abi.encodeCall( IGraphRule.processFollow, ( - configSalt, - originalMsgSender, - followerAccount, - accountToFollow, - primitiveCustomParams, - ruleCustomParams + rule.configSalt, + processParams.originalMsgSender, + processParams.sourceAccount, + processParams.targetAccount, + processParams.primitiveCustomParams, + ruleParams ) ) ); @@ -208,39 +243,37 @@ abstract contract RuleBasedGraph is IGraph, RuleBasedPrimitive { address followerAccount, address accountToFollow, KeyValue[] calldata primitiveCustomParams, - RuleProcessingParams[] calldata ruleProcessingParams + RuleProcessingParams[] calldata rulesProcessingParams ) internal { _processFollow( $graphRulesStorage(), _encodeAndCallGraphProcessFollow, IGraphRule.processFollow.selector, - originalMsgSender, - followerAccount, - accountToFollow, - primitiveCustomParams, - ruleProcessingParams + ProcessParams({ + originalMsgSender: originalMsgSender, + sourceAccount: followerAccount, + targetAccount: accountToFollow, + primitiveCustomParams: primitiveCustomParams, + rulesProcessingParams: rulesProcessingParams + }) ); } function _encodeAndCallGraphProcessUnfollow( - address rule, - bytes32 configSalt, - address originalMsgSender, - address followerAccount, - address accountToUnfollow, - KeyValue[] calldata primitiveCustomParams, - KeyValue[] memory ruleCustomParams + Rule memory rule, + ProcessParams memory processParams, + KeyValue[] memory ruleParams ) internal returns (bool, bytes memory) { - return rule.safecall( + return rule.ruleAddress.safecall( abi.encodeCall( IGraphRule.processUnfollow, ( - configSalt, - originalMsgSender, - followerAccount, - accountToUnfollow, - primitiveCustomParams, - ruleCustomParams + rule.configSalt, + processParams.originalMsgSender, + processParams.sourceAccount, + processParams.targetAccount, + processParams.primitiveCustomParams, + ruleParams ) ) ); @@ -251,38 +284,36 @@ abstract contract RuleBasedGraph is IGraph, RuleBasedPrimitive { address followerAccount, address accountToUnfollow, KeyValue[] calldata primitiveCustomParams, - RuleProcessingParams[] calldata ruleProcessingParams + RuleProcessingParams[] calldata rulesProcessingParams ) internal { _processUnfollow( $graphRulesStorage(), _encodeAndCallGraphProcessUnfollow, - originalMsgSender, - followerAccount, - accountToUnfollow, - primitiveCustomParams, - ruleProcessingParams + ProcessParams({ + originalMsgSender: originalMsgSender, + sourceAccount: followerAccount, + targetAccount: accountToUnfollow, + primitiveCustomParams: primitiveCustomParams, + rulesProcessingParams: rulesProcessingParams + }) ); } function _encodeAndCallAccountProcessFollow( - address rule, - bytes32 configSalt, - address originalMsgSender, - address followerAccount, - address accountToFollow, - KeyValue[] calldata primitiveCustomParams, - KeyValue[] memory ruleCustomParams + Rule memory rule, + ProcessParams memory processParams, + KeyValue[] memory ruleParams ) internal returns (bool, bytes memory) { - return rule.safecall( + return rule.ruleAddress.safecall( abi.encodeCall( IFollowRule.processFollow, ( - configSalt, - originalMsgSender, - followerAccount, - accountToFollow, - primitiveCustomParams, - ruleCustomParams + rule.configSalt, + processParams.originalMsgSender, + processParams.sourceAccount, + processParams.targetAccount, + processParams.primitiveCustomParams, + ruleParams ) ) ); @@ -293,143 +324,75 @@ abstract contract RuleBasedGraph is IGraph, RuleBasedPrimitive { address followerAccount, address accountToFollow, KeyValue[] calldata primitiveCustomParams, - RuleProcessingParams[] calldata ruleProcessingParams + RuleProcessingParams[] calldata rulesProcessingParams ) internal { _processFollow( $followRulesStorage(accountToFollow), _encodeAndCallAccountProcessFollow, IFollowRule.processFollow.selector, - originalMsgSender, - followerAccount, - accountToFollow, - primitiveCustomParams, - ruleProcessingParams + ProcessParams({ + originalMsgSender: originalMsgSender, + sourceAccount: followerAccount, + targetAccount: accountToFollow, + primitiveCustomParams: primitiveCustomParams, + rulesProcessingParams: rulesProcessingParams + }) ); } function _processUnfollow( RulesStorage storage rulesStorage, - function(address,bytes32,address,address,address,KeyValue[] calldata,KeyValue[] memory) internal returns (bool,bytes memory) - encodeAndCall, - address originalMsgSender, - address followerAccount, - address accountToUnfollow, - KeyValue[] calldata primitiveCustomParams, - RuleProcessingParams[] calldata rulesProcessingParams + function(Rule memory,ProcessParams memory,KeyValue[] memory) internal returns (bool,bytes memory) encodeAndCall, + ProcessParams memory processParams ) internal { bytes4 ruleSelector = IGraphRule.processUnfollow.selector; + Rule memory rule; + KeyValue[] memory ruleParams; // Check required rules (AND-combined rules) for (uint256 i = 0; i < rulesStorage.requiredRules[ruleSelector].length; i++) { - Rule memory rule = rulesStorage.requiredRules[ruleSelector][i]; - // TODO: Think how to put this loop into a library (all the rules use it) - for (uint256 j = 0; j < rulesProcessingParams.length; j++) { - KeyValue[] memory ruleCustomParams = new KeyValue[](0); - if ( - rulesProcessingParams[j].ruleAddress == rule.ruleAddress - && rulesProcessingParams[j].configSalt == rule.configSalt - ) { - ruleCustomParams = rulesProcessingParams[j].ruleParams; - } - (bool callNotReverted,) = encodeAndCall( - rule.ruleAddress, - rule.configSalt, - originalMsgSender, - followerAccount, - accountToUnfollow, - primitiveCustomParams, - ruleCustomParams - ); - require(callNotReverted, "Some required rule failed"); - } + rule = rulesStorage.requiredRules[ruleSelector][i]; + ruleParams = _getRuleParamsOrEmptyArray(rule, processParams.rulesProcessingParams); + (bool callSucceeded,) = encodeAndCall(rule, processParams, ruleParams); + require(callSucceeded, Errors.RequiredRuleReverted()); } + // Check any-of rules (OR-combined rules) for (uint256 i = 0; i < rulesStorage.anyOfRules[ruleSelector].length; i++) { - Rule memory rule = rulesStorage.anyOfRules[ruleSelector][i]; - for (uint256 j = 0; j < rulesProcessingParams.length; j++) { - KeyValue[] memory ruleCustomParams = new KeyValue[](0); - if ( - rulesProcessingParams[j].ruleAddress == rule.ruleAddress - && rulesProcessingParams[j].configSalt == rule.configSalt - ) { - ruleCustomParams = rulesProcessingParams[j].ruleParams; - } - (bool callNotReverted,) = encodeAndCall( - rule.ruleAddress, - rule.configSalt, - originalMsgSender, - followerAccount, - accountToUnfollow, - primitiveCustomParams, - ruleCustomParams - ); - if (callNotReverted) { - return; // If any of the OR-combined rules passed, it means they succeed and we can return - } + rule = rulesStorage.anyOfRules[ruleSelector][i]; + ruleParams = _getRuleParamsOrEmptyArray(rule, processParams.rulesProcessingParams); + (bool callSucceeded,) = encodeAndCall(rule, processParams, ruleParams); + if (callSucceeded) { + return; // If any of the OR-combined rules passed, it means they succeed and we can return } } // If there are any-of rules and it reached this point, it means all of them failed. - require($graphRulesStorage().anyOfRules[ruleSelector].length == 0, "All of the any-of rules failed"); + require(rulesStorage.anyOfRules[ruleSelector].length == 0, Errors.AllAnyOfRulesReverted()); } function _processFollow( RulesStorage storage rulesStorage, - function(address,bytes32,address,address,address,KeyValue[] calldata,KeyValue[] memory) internal returns (bool,bytes memory) - encodeAndCall, + function(Rule memory,ProcessParams memory,KeyValue[] memory) internal returns (bool,bytes memory) encodeAndCall, bytes4 ruleSelector, - address originalMsgSender, - address followerAccount, - address accountToFollow, - KeyValue[] calldata primitiveCustomParams, - RuleProcessingParams[] calldata rulesProcessingParams + ProcessParams memory processParams ) internal { + Rule memory rule; + KeyValue[] memory ruleParams; // Check required rules (AND-combined rules) for (uint256 i = 0; i < rulesStorage.requiredRules[ruleSelector].length; i++) { - Rule memory rule = rulesStorage.requiredRules[ruleSelector][i]; - // TODO: Think how to put this loop into a library (all the rules use it) - for (uint256 j = 0; j < rulesProcessingParams.length; j++) { - KeyValue[] memory ruleCustomParams = new KeyValue[](0); - if ( - rulesProcessingParams[j].ruleAddress == rule.ruleAddress - && rulesProcessingParams[j].configSalt == rule.configSalt - ) { - ruleCustomParams = rulesProcessingParams[j].ruleParams; - } - (bool callNotReverted,) = encodeAndCall( - rule.ruleAddress, - rule.configSalt, - originalMsgSender, - followerAccount, - accountToFollow, - primitiveCustomParams, - ruleCustomParams - ); - require(callNotReverted, "Some required rule failed"); - } + rule = rulesStorage.requiredRules[ruleSelector][i]; + ruleParams = _getRuleParamsOrEmptyArray(rule, processParams.rulesProcessingParams); + (bool callSucceeded,) = encodeAndCall(rule, processParams, ruleParams); + require(callSucceeded, Errors.RequiredRuleReverted()); } + // Check any-of rules (OR-combined rules) for (uint256 i = 0; i < rulesStorage.anyOfRules[ruleSelector].length; i++) { - Rule memory rule = rulesStorage.anyOfRules[ruleSelector][i]; - for (uint256 j = 0; j < rulesProcessingParams.length; j++) { - KeyValue[] memory ruleCustomParams = new KeyValue[](0); - if ( - rulesProcessingParams[j].ruleAddress == rule.ruleAddress - && rulesProcessingParams[j].configSalt == rule.configSalt - ) { - ruleCustomParams = rulesProcessingParams[j].ruleParams; - } - (bool callNotReverted,) = encodeAndCall( - rule.ruleAddress, - rule.configSalt, - originalMsgSender, - followerAccount, - accountToFollow, - primitiveCustomParams, - ruleCustomParams - ); - if (callNotReverted) { - return; // If any of the OR-combined rules passed, it means they succeed and we can return - } + rule = rulesStorage.anyOfRules[ruleSelector][i]; + ruleParams = _getRuleParamsOrEmptyArray(rule, processParams.rulesProcessingParams); + (bool callSucceeded,) = encodeAndCall(rule, processParams, ruleParams); + if (callSucceeded) { + return; // If any of the OR-combined rules passed, it means they succeed and we can return } } // If there are any-of rules and it reached this point, it means all of them failed. - require($graphRulesStorage().anyOfRules[ruleSelector].length == 0, "All of the any-of rules failed"); + require(rulesStorage.anyOfRules[ruleSelector].length == 0, Errors.AllAnyOfRulesReverted()); } } diff --git a/contracts/core/primitives/group/Group.sol b/contracts/core/primitives/group/Group.sol index 09222c1a..30ebbe8d 100644 --- a/contracts/core/primitives/group/Group.sol +++ b/contracts/core/primitives/group/Group.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {Membership, IGroup} from "contracts/core/interfaces/IGroup.sol"; import {GroupCore as Core} from "contracts/core/primitives/group/GroupCore.sol"; @@ -14,6 +14,7 @@ import {IGroupRule} from "contracts/core/interfaces/IGroupRule.sol"; import {SourceStampBased} from "contracts/core/base/SourceStampBased.sol"; import {MetadataBased} from "contracts/core/base/MetadataBased.sol"; import {Initializable} from "contracts/core/upgradeability/Initializable.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; contract Group is IGroup, @@ -70,10 +71,17 @@ contract Group is _requireAccess(msg.sender, PID__SET_METADATA); } - function _beforeChangePrimitiveRules(RuleChange[] calldata /* ruleChanges */ ) internal virtual override { + function _beforeChangePrimitiveRules(RuleChange[] memory /* ruleChanges */ ) internal virtual override { _requireAccess(msg.sender, PID__CHANGE_RULES); } + function _beforeChangeEntityRules(uint256 entityId, RuleChange[] memory ruleChanges) + internal + pure + virtual + override + {} + function setExtraData(KeyValue[] calldata extraDataToSet) external override { _requireAccess(msg.sender, PID__SET_EXTRA_DATA); for (uint256 i = 0; i < extraDataToSet.length; i++) { @@ -127,7 +135,7 @@ contract Group is KeyValue[] calldata customParams, RuleProcessingParams[] calldata ruleProcessingParams ) external override { - require(msg.sender == account); + require(msg.sender == account, Errors.InvalidMsgSender()); uint256 membershipId = Core._grantMembership(account); _processMemberJoining(msg.sender, account, customParams, ruleProcessingParams); address source = _processSourceStamp(membershipId, customParams); @@ -139,7 +147,7 @@ contract Group is KeyValue[] calldata customParams, RuleProcessingParams[] calldata ruleProcessingParams ) external override { - require(msg.sender == account); + require(msg.sender == account, Errors.InvalidMsgSender()); uint256 membershipId = Core._revokeMembership(account); _processMemberLeaving(msg.sender, account, customParams, ruleProcessingParams); address source = _processSourceStamp(membershipId, customParams); @@ -158,19 +166,19 @@ contract Group is function getMembership(address account) external view override returns (Membership memory) { Membership memory membership = Core._getMembership(account); - require(membership.id != 0, "NOT_A_MEMBER"); + require(membership.id != 0, Errors.DoesNotExist()); return membership; } function getMembershipTimestamp(address account) external view override returns (uint256) { Membership memory membership = Core._getMembership(account); - require(membership.id != 0, "NOT_A_MEMBER"); + require(membership.id != 0, Errors.DoesNotExist()); return membership.timestamp; } function getMembershipId(address account) external view override returns (uint256) { uint256 membershipId = Core.$storage().memberships[account].id; - require(membershipId != 0, "NOT_A_MEMBER"); + require(membershipId != 0, Errors.DoesNotExist()); return membershipId; } diff --git a/contracts/core/primitives/group/GroupCore.sol b/contracts/core/primitives/group/GroupCore.sol index 3ac777e7..2a58b2f3 100644 --- a/contracts/core/primitives/group/GroupCore.sol +++ b/contracts/core/primitives/group/GroupCore.sol @@ -1,8 +1,9 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.17; +pragma solidity ^0.8.26; import {Membership} from "contracts/core/interfaces/IGroup.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; library GroupCore { // Storage @@ -35,14 +36,14 @@ library GroupCore { function _grantMembership(address account) internal returns (uint256) { uint256 membershipId = ++$storage().lastMemberIdAssigned; $storage().numberOfMembers++; - require($storage().memberships[account].id == 0); // Must not be a member yet + require($storage().memberships[account].id == 0, Errors.RedundantStateChange()); // Must not be a member yet $storage().memberships[account] = Membership(membershipId, block.timestamp); return membershipId; } function _revokeMembership(address account) internal returns (uint256) { uint256 membershipId = $storage().memberships[account].id; - require(membershipId != 0); // Must be a member + require(membershipId != 0, Errors.RedundantStateChange()); // Must be a member $storage().numberOfMembers--; delete $storage().memberships[account]; return membershipId; diff --git a/contracts/core/primitives/group/RuleBasedGroup.sol b/contracts/core/primitives/group/RuleBasedGroup.sol index 368cd8fd..76ba8e26 100644 --- a/contracts/core/primitives/group/RuleBasedGroup.sol +++ b/contracts/core/primitives/group/RuleBasedGroup.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IGroupRule} from "contracts/core/interfaces/IGroupRule.sol"; import {IGroup} from "contracts/core/interfaces/IGroup.sol"; @@ -8,6 +8,7 @@ import {RulesStorage, RulesLib} from "contracts/core/libraries/RulesLib.sol"; import {RuleChange, RuleProcessingParams, Rule, KeyValue} from "contracts/core/types/Types.sol"; import {RuleBasedPrimitive} from "contracts/core/base/RuleBasedPrimitive.sol"; import {CallLib} from "contracts/core/libraries/CallLib.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; abstract contract RuleBasedGroup is IGroup, RuleBasedPrimitive { using RulesLib for RulesStorage; @@ -45,7 +46,7 @@ abstract contract RuleBasedGroup is IGroup, RuleBasedPrimitive { return selectors; } - function _encodePrimitiveConfigureCall(bytes32 configSalt, KeyValue[] calldata ruleParams) + function _encodePrimitiveConfigureCall(bytes32 configSalt, KeyValue[] memory ruleParams) internal pure override @@ -54,11 +55,18 @@ abstract contract RuleBasedGroup is IGroup, RuleBasedPrimitive { return abi.encodeCall(IGroupRule.configure, (configSalt, ruleParams)); } + function _encodeEntityConfigureCall(uint256 entityId, bytes32 configSalt, KeyValue[] memory ruleParams) + internal + pure + override + returns (bytes memory) + {} + function _emitPrimitiveRuleConfiguredEvent( bool wasAlreadyConfigured, address ruleAddress, bytes32 configSalt, - KeyValue[] calldata ruleParams + KeyValue[] memory ruleParams ) internal override { if (wasAlreadyConfigured) { emit IGroup.Lens_Group_RuleReconfigured(ruleAddress, configSalt, ruleParams); @@ -81,6 +89,23 @@ abstract contract RuleBasedGroup is IGroup, RuleBasedPrimitive { } } + function _emitEntityRuleConfiguredEvent( + bool wasAlreadyConfigured, + uint256 entityId, + address ruleAddress, + bytes32 configSalt, + KeyValue[] memory ruleParams + ) internal override {} + + function _emitEntityRuleSelectorEvent( + bool enabled, + uint256 entityId, + address ruleAddress, + bytes32 configSalt, + bool isRequired, + bytes4 selector + ) internal override {} + function _amountOfRules(bytes4 ruleSelector) internal view returns (uint256) { return $groupRulesStorage()._getRulesArray(ruleSelector, false).length + $groupRulesStorage()._getRulesArray(ruleSelector, true).length; @@ -99,17 +124,20 @@ abstract contract RuleBasedGroup is IGroup, RuleBasedPrimitive { //////////////////////////// PROCESSING FUNCTIONS //////////////////////////// function _encodeAndCallProcessMemberRemoval( - address rule, - bytes32 configSalt, - address originalMsgSender, - address account, - KeyValue[] calldata primitiveCustomParams, - KeyValue[] memory ruleCustomParams + Rule memory rule, + ProcessParams memory processParams, + KeyValue[] memory ruleParams ) internal returns (bool, bytes memory) { - return rule.safecall( + return rule.ruleAddress.safecall( abi.encodeCall( IGroupRule.processRemoval, - (configSalt, originalMsgSender, account, primitiveCustomParams, ruleCustomParams) + ( + rule.configSalt, + processParams.originalMsgSender, + processParams.account, + processParams.primitiveCustomParams, + ruleParams + ) ) ); } @@ -122,26 +150,31 @@ abstract contract RuleBasedGroup is IGroup, RuleBasedPrimitive { ) internal { _processGroupRule( _encodeAndCallProcessMemberRemoval, - IGroupRule.processRemoval.selector, - originalMsgSender, - account, - primitiveCustomParams, - ruleProcessingParams + ProcessParams({ + ruleSelector: IGroupRule.processRemoval.selector, + originalMsgSender: originalMsgSender, + account: account, + primitiveCustomParams: primitiveCustomParams, + rulesProcessingParams: ruleProcessingParams + }) ); } function _encodeAndCallProcessMemberAddition( - address rule, - bytes32 configSalt, - address originalMsgSender, - address account, - KeyValue[] calldata primitiveCustomParams, - KeyValue[] memory ruleCustomParams + Rule memory rule, + ProcessParams memory processParams, + KeyValue[] memory ruleParams ) internal returns (bool, bytes memory) { - return rule.safecall( + return rule.ruleAddress.safecall( abi.encodeCall( IGroupRule.processAddition, - (configSalt, originalMsgSender, account, primitiveCustomParams, ruleCustomParams) + ( + rule.configSalt, + processParams.originalMsgSender, + processParams.account, + processParams.primitiveCustomParams, + ruleParams + ) ) ); } @@ -154,24 +187,26 @@ abstract contract RuleBasedGroup is IGroup, RuleBasedPrimitive { ) internal { _processGroupRule( _encodeAndCallProcessMemberAddition, - IGroupRule.processAddition.selector, - originalMsgSender, - account, - primitiveCustomParams, - ruleProcessingParams + ProcessParams({ + ruleSelector: IGroupRule.processAddition.selector, + originalMsgSender: originalMsgSender, + account: account, + primitiveCustomParams: primitiveCustomParams, + rulesProcessingParams: ruleProcessingParams + }) ); } function _encodeAndCallProcessMemberJoining( - address rule, - bytes32 configSalt, - address, /* originalMsgSender */ - address account, - KeyValue[] calldata primitiveCustomParams, - KeyValue[] memory ruleCustomParams + Rule memory rule, + ProcessParams memory processParams, + KeyValue[] memory ruleParams ) internal returns (bool, bytes memory) { - return rule.safecall( - abi.encodeCall(IGroupRule.processJoining, (configSalt, account, primitiveCustomParams, ruleCustomParams)) + return rule.ruleAddress.safecall( + abi.encodeCall( + IGroupRule.processJoining, + (rule.configSalt, processParams.account, processParams.primitiveCustomParams, ruleParams) + ) ); } @@ -183,24 +218,26 @@ abstract contract RuleBasedGroup is IGroup, RuleBasedPrimitive { ) internal { _processGroupRule( _encodeAndCallProcessMemberJoining, - IGroupRule.processJoining.selector, - originalMsgSender, - account, - primitiveCustomParams, - ruleProcessingParams + ProcessParams({ + ruleSelector: IGroupRule.processJoining.selector, + originalMsgSender: originalMsgSender, + account: account, + primitiveCustomParams: primitiveCustomParams, + rulesProcessingParams: ruleProcessingParams + }) ); } function _encodeAndCallProcessMemberLeaving( - address rule, - bytes32 configSalt, - address, /* originalMsgSender */ - address account, - KeyValue[] calldata primitiveCustomParams, - KeyValue[] memory ruleCustomParams + Rule memory rule, + ProcessParams memory processParams, + KeyValue[] memory ruleParams ) internal returns (bool, bytes memory) { - return rule.safecall( - abi.encodeCall(IGroupRule.processLeaving, (configSalt, account, primitiveCustomParams, ruleCustomParams)) + return rule.ruleAddress.safecall( + abi.encodeCall( + IGroupRule.processLeaving, + (rule.configSalt, processParams.account, processParams.primitiveCustomParams, ruleParams) + ) ); } @@ -212,60 +249,47 @@ abstract contract RuleBasedGroup is IGroup, RuleBasedPrimitive { ) internal { _processGroupRule( _encodeAndCallProcessMemberLeaving, - IGroupRule.processLeaving.selector, - originalMsgSender, - account, - primitiveCustomParams, - ruleProcessingParams + ProcessParams({ + ruleSelector: IGroupRule.processLeaving.selector, + originalMsgSender: originalMsgSender, + account: account, + primitiveCustomParams: primitiveCustomParams, + rulesProcessingParams: ruleProcessingParams + }) ); } + struct ProcessParams { + bytes4 ruleSelector; + address originalMsgSender; + address account; + KeyValue[] primitiveCustomParams; + RuleProcessingParams[] rulesProcessingParams; + } + function _processGroupRule( - function(address,bytes32,address,address,KeyValue[] calldata,KeyValue[] memory) internal returns (bool,bytes memory) - encodeAndCall, - bytes4 ruleSelector, - address originalMsgSender, - address account, - KeyValue[] calldata primitiveCustomParams, - RuleProcessingParams[] calldata rulesProcessingParams + function(Rule memory,ProcessParams memory,KeyValue[] memory) internal returns (bool,bytes memory) encodeAndCall, + ProcessParams memory processParams ) private { + Rule memory rule; + KeyValue[] memory ruleParams; // Check required rules (AND-combined rules) - for (uint256 i = 0; i < $groupRulesStorage().requiredRules[ruleSelector].length; i++) { - Rule memory rule = $groupRulesStorage().requiredRules[ruleSelector][i]; - for (uint256 j = 0; j < rulesProcessingParams.length; j++) { - KeyValue[] memory ruleParams = new KeyValue[](0); - if ( - rulesProcessingParams[j].ruleAddress == rule.ruleAddress - && rulesProcessingParams[j].configSalt == rule.configSalt - ) { - ruleParams = rulesProcessingParams[j].ruleParams; - } - (bool callNotReverted,) = encodeAndCall( - rule.ruleAddress, rule.configSalt, originalMsgSender, account, primitiveCustomParams, ruleParams - ); - require(callNotReverted, "Some required rule failed"); - } + for (uint256 i = 0; i < $groupRulesStorage().requiredRules[processParams.ruleSelector].length; i++) { + rule = $groupRulesStorage().requiredRules[processParams.ruleSelector][i]; + ruleParams = _getRuleParamsOrEmptyArray(rule, processParams.rulesProcessingParams); + (bool callSucceeded,) = encodeAndCall(rule, processParams, ruleParams); + require(callSucceeded, Errors.RequiredRuleReverted()); } // Check any-of rules (OR-combined rules) - for (uint256 i = 0; i < $groupRulesStorage().anyOfRules[ruleSelector].length; i++) { - Rule memory rule = $groupRulesStorage().anyOfRules[ruleSelector][i]; - for (uint256 j = 0; j < rulesProcessingParams.length; j++) { - KeyValue[] memory ruleParams = new KeyValue[](0); - if ( - rulesProcessingParams[j].ruleAddress == rule.ruleAddress - && rulesProcessingParams[j].configSalt == rule.configSalt - ) { - ruleParams = rulesProcessingParams[j].ruleParams; - } - (bool callNotReverted,) = encodeAndCall( - rule.ruleAddress, rule.configSalt, originalMsgSender, account, primitiveCustomParams, ruleParams - ); - if (callNotReverted) { - return; // If any of the OR-combined rules passed, it means they succeed and we can return - } + for (uint256 i = 0; i < $groupRulesStorage().anyOfRules[processParams.ruleSelector].length; i++) { + rule = $groupRulesStorage().anyOfRules[processParams.ruleSelector][i]; + ruleParams = _getRuleParamsOrEmptyArray(rule, processParams.rulesProcessingParams); + (bool callSucceeded,) = encodeAndCall(rule, processParams, ruleParams); + if (callSucceeded) { + return; // If any of the OR-combined rules passed, it means they succeed and we can return } } // If there are any-of rules and it reached this point, it means all of them failed. - require($groupRulesStorage().anyOfRules[ruleSelector].length == 0, "All of the any-of rules failed"); + require($groupRulesStorage().anyOfRules[processParams.ruleSelector].length == 0, Errors.AllAnyOfRulesReverted()); } } diff --git a/contracts/core/primitives/namespace/LensUsernameTokenURIProvider.sol b/contracts/core/primitives/namespace/LensUsernameTokenURIProvider.sol index fa225812..6095a2c7 100644 --- a/contracts/core/primitives/namespace/LensUsernameTokenURIProvider.sol +++ b/contracts/core/primitives/namespace/LensUsernameTokenURIProvider.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {ITokenURIProvider} from "contracts/core/interfaces/ITokenURIProvider.sol"; import "@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol"; diff --git a/contracts/core/primitives/namespace/Namespace.sol b/contracts/core/primitives/namespace/Namespace.sol index 29fb239d..bf096e76 100644 --- a/contracts/core/primitives/namespace/Namespace.sol +++ b/contracts/core/primitives/namespace/Namespace.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {NamespaceCore as Core} from "contracts/core/primitives/namespace/NamespaceCore.sol"; import {INamespace} from "contracts/core/interfaces/INamespace.sol"; @@ -16,6 +16,7 @@ import {ITokenURIProvider} from "contracts/core/interfaces/ITokenURIProvider.sol import {SourceStampBased} from "contracts/core/base/SourceStampBased.sol"; import {MetadataBased} from "contracts/core/base/MetadataBased.sol"; import {Initializable} from "contracts/core/upgradeability/Initializable.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; contract Namespace is INamespace, @@ -84,25 +85,33 @@ contract Namespace is _requireAccess(msg.sender, PID__SET_METADATA); } - function _beforeTokenURIProviderSet(ITokenURIProvider /* tokenURIProvider */ ) internal view override { + function _beforeTokenURIProviderSet(ITokenURIProvider /* tokenURIProvider */ ) internal view virtual override { _requireAccess(msg.sender, PID__SET_TOKEN_URI_PROVIDER); } - function _beforeChangePrimitiveRules(RuleChange[] calldata /* ruleChanges */ ) internal virtual override { + function _beforeChangePrimitiveRules(RuleChange[] memory /* ruleChanges */ ) internal view virtual override { _requireAccess(msg.sender, PID__CHANGE_RULES); } + + function _beforeChangeEntityRules(uint256 entityId, RuleChange[] memory ruleChanges) + internal + pure + virtual + override + {} + // Permissionless functions function createAndAssignUsername( address account, - string calldata username, + string memory username, KeyValue[] calldata customParams, RuleProcessingParams[] calldata unassigningProcessingParams, RuleProcessingParams[] calldata creationProcessingParams, RuleProcessingParams[] calldata assigningProcessingParams, - KeyValue[] calldata extraData + KeyValue[] memory extraData ) external { - require(msg.sender == account); // msg.sender must be the account + require(msg.sender == account, Errors.InvalidMsgSender()); uint256 id = _computeId(username); _safeMint(account, id); _idToUsername[id] = username; @@ -124,7 +133,7 @@ contract Namespace is RuleProcessingParams[] calldata ruleProcessingParams, KeyValue[] calldata extraData ) external override { - require(msg.sender == account); // msg.sender must be the account + require(msg.sender == account, Errors.InvalidMsgSender()); uint256 id = _computeId(username); _safeMint(account, id); _idToUsername[id] = username; @@ -142,8 +151,8 @@ contract Namespace is RuleProcessingParams[] calldata removalRuleProcessingParams ) external override { uint256 id = _computeId(username); - address owner = _ownerOf(id); - require(msg.sender == owner); // msg.sender must be the owner of the username + address owner = ownerOf(id); + require(msg.sender == owner, Errors.InvalidMsgSender()); // msg.sender must be the owner of the username _processRemoval(msg.sender, username, customParams, removalRuleProcessingParams); address source = _processSourceStamp(id, customParams); _unassignIfAssigned(username, customParams, unassigningRuleProcessingParams, source); @@ -160,9 +169,11 @@ contract Namespace is RuleProcessingParams[] calldata unassignUsernameRuleProcessingParams, RuleProcessingParams[] calldata assignRuleProcessingParams ) external override { - require(msg.sender == account); // msg.sender must be the account uint256 id = _computeId(username); - require(account == _ownerOf(id)); // account should own the tokenized username + // account should own the tokenized username and be the msg.sender + require(msg.sender == ownerOf(id) && msg.sender == account, Errors.InvalidMsgSender()); + // Check if username is not already assigned to this account + require(account != Core.$storage().usernameToAccount[username], Errors.RedundantStateChange()); address source = _processSourceStamp(id, customParams); _unassignIfAssigned(account, customParams, unassignAccountRuleProcessingParams, source); _unassignIfAssigned(username, customParams, unassignUsernameRuleProcessingParams, source); @@ -178,7 +189,7 @@ contract Namespace is ) external override { address account = Core.$storage().usernameToAccount[username]; uint256 id = _computeId(username); - require(msg.sender == account || msg.sender == _ownerOf(id)); + require(msg.sender == ownerOf(id) || msg.sender == account, Errors.InvalidMsgSender()); Core._unassignUsername(username); _processUnassigning(msg.sender, account, username, customParams, ruleProcessingParams); address source = _processSourceStamp(id, customParams); @@ -209,7 +220,7 @@ contract Namespace is function setUsernameExtraData(string calldata username, KeyValue[] calldata extraDataToSet) external { uint256 id = _computeId(username); address owner = _ownerOf(id); - require(msg.sender == owner); + require(msg.sender == owner, Errors.InvalidMsgSender()); _decodeAndSetUsernameExtraData(id, extraDataToSet); } @@ -275,13 +286,13 @@ contract Namespace is function usernameOf(address user) external view returns (string memory) { string memory username = Core.$storage().accountToUsername[user]; - require(bytes(username).length != 0, "NO_USERNAME_ASSIGNED"); + require(bytes(username).length != 0, Errors.DoesNotExist()); return username; } function accountOf(string memory username) external view returns (address) { uint256 tokenId = _computeId(username); - require(_exists(tokenId), "NO_SUCH_USERNAME"); + require(_exists(tokenId), Errors.DoesNotExist()); return Core.$storage().usernameToAccount[username]; } @@ -298,4 +309,17 @@ contract Namespace is address owner = ownerOf(tokenId); return _getEntityExtraData(owner, tokenId, key); } + + function exists(string calldata username) external view override returns (bool) { + uint256 tokenId = _computeId(username); + return _exists(tokenId); + } + + function exists(uint256 tokenId) external view override returns (bool) { + return _exists(tokenId); + } + + function getUsernameTokenId(string calldata username) external pure returns (uint256) { + return _computeId(username); + } } diff --git a/contracts/core/primitives/namespace/NamespaceCore.sol b/contracts/core/primitives/namespace/NamespaceCore.sol index 10cca5e0..f0af1289 100644 --- a/contracts/core/primitives/namespace/NamespaceCore.sol +++ b/contracts/core/primitives/namespace/NamespaceCore.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; + +import {Errors} from "contracts/core/types/Errors.sol"; library NamespaceCore { // Storage @@ -21,49 +23,31 @@ library NamespaceCore { } } - // External functions - Use these functions to be called through DELEGATECALL - - function createUsername(string memory username) external { - _createUsername(username); - } - - function removeUsername(string memory username) external { - _removeUsername(username); - } - - function assignUsername(address account, string memory username) external { - _assignUsername(account, username); - } - - function unassignUsername(string memory username) external { - _unassignUsername(username); - } - - // Internal functions - Use these functions to be called as an inlined library + // Internal functions function _createUsername(string memory username) internal { - require(!$storage().usernameExists[username]); // Username must not exist yet - require(bytes(username).length > 0); // Username must not be empty + require(!$storage().usernameExists[username], Errors.AlreadyExists()); // Username must not exist yet + require(bytes(username).length > 0, Errors.InvalidParameter()); // Username must not be empty $storage().usernameExists[username] = true; } function _removeUsername(string memory username) internal { - require($storage().usernameExists[username]); // Username must exist - require($storage().usernameToAccount[username] == address(0)); // Username must not be assigned + require($storage().usernameExists[username], Errors.DoesNotExist()); // Username must exist + require($storage().usernameToAccount[username] == address(0), Errors.UsernameAssigned()); // Username must not be assigned $storage().usernameExists[username] = false; } function _assignUsername(address account, string memory username) internal { - require($storage().usernameExists[username]); // Username must exist - require($storage().usernameToAccount[username] == address(0)); // Username must not be assigned yet - require(bytes($storage().accountToUsername[account]).length == 0); // Account must not have a username yet + require($storage().usernameExists[username], Errors.DoesNotExist()); // Username must exist + require($storage().usernameToAccount[username] == address(0), Errors.UsernameAssigned()); // Username must not be assigned yet + require(bytes($storage().accountToUsername[account]).length == 0, Errors.UsernameAssigned()); // Account must not have a username yet $storage().usernameToAccount[username] = account; $storage().accountToUsername[account] = username; } function _unassignUsername(string memory username) internal { address account = $storage().usernameToAccount[username]; - require(account != address(0)); // Username must be assigned + require(account != address(0), Errors.RedundantStateChange()); // Username must be assigned delete $storage().accountToUsername[account]; delete $storage().usernameToAccount[username]; } diff --git a/contracts/core/primitives/namespace/RuleBasedNamespace.sol b/contracts/core/primitives/namespace/RuleBasedNamespace.sol index dbdff676..32e0feb4 100644 --- a/contracts/core/primitives/namespace/RuleBasedNamespace.sol +++ b/contracts/core/primitives/namespace/RuleBasedNamespace.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {INamespaceRule} from "contracts/core/interfaces/INamespaceRule.sol"; import {RulesStorage, RulesLib} from "contracts/core/libraries/RulesLib.sol"; @@ -8,6 +8,7 @@ import {RuleChange, RuleProcessingParams, Rule, KeyValue} from "contracts/core/t import {INamespace} from "contracts/core/interfaces/INamespace.sol"; import {RuleBasedPrimitive} from "contracts/core/base/RuleBasedPrimitive.sol"; import {CallLib} from "contracts/core/libraries/CallLib.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; abstract contract RuleBasedNamespace is INamespace, RuleBasedPrimitive { using RulesLib for RulesStorage; @@ -45,7 +46,7 @@ abstract contract RuleBasedNamespace is INamespace, RuleBasedPrimitive { return selectors; } - function _encodePrimitiveConfigureCall(bytes32 configSalt, KeyValue[] calldata ruleParams) + function _encodePrimitiveConfigureCall(bytes32 configSalt, KeyValue[] memory ruleParams) internal pure override @@ -54,11 +55,18 @@ abstract contract RuleBasedNamespace is INamespace, RuleBasedPrimitive { return abi.encodeCall(INamespaceRule.configure, (configSalt, ruleParams)); } + function _encodeEntityConfigureCall(uint256 entityId, bytes32 configSalt, KeyValue[] memory ruleParams) + internal + pure + override + returns (bytes memory) + {} + function _emitPrimitiveRuleConfiguredEvent( bool wasAlreadyConfigured, address ruleAddress, bytes32 configSalt, - KeyValue[] calldata ruleParams + KeyValue[] memory ruleParams ) internal override { if (wasAlreadyConfigured) { emit INamespace.Lens_Namespace_RuleReconfigured(ruleAddress, configSalt, ruleParams); @@ -81,6 +89,23 @@ abstract contract RuleBasedNamespace is INamespace, RuleBasedPrimitive { } } + function _emitEntityRuleConfiguredEvent( + bool wasAlreadyConfigured, + uint256 entityId, + address ruleAddress, + bytes32 configSalt, + KeyValue[] memory ruleParams + ) internal override {} + + function _emitEntityRuleSelectorEvent( + bool enabled, + uint256 entityId, + address ruleAddress, + bytes32 configSalt, + bool isRequired, + bytes4 selector + ) internal override {} + function _amountOfRules(bytes4 ruleSelector) internal view returns (uint256) { return $namespaceRulesStorage()._getRulesArray(ruleSelector, false).length + $namespaceRulesStorage()._getRulesArray(ruleSelector, true).length; @@ -99,18 +124,21 @@ abstract contract RuleBasedNamespace is INamespace, RuleBasedPrimitive { //////////////////////////// PROCESSING FUNCTIONS //////////////////////////// function _encodeAndCallProcessCreation( - address rule, - bytes32 configSalt, - address originalMsgSender, - address account, - string memory username, - KeyValue[] calldata primitiveCustomParams, - KeyValue[] memory ruleCustomParams + Rule memory rule, + ProcessParams memory processParams, + KeyValue[] memory ruleParams ) internal returns (bool, bytes memory) { - return rule.safecall( + return rule.ruleAddress.safecall( abi.encodeCall( INamespaceRule.processCreation, - (configSalt, originalMsgSender, account, username, primitiveCustomParams, ruleCustomParams) + ( + rule.configSalt, + processParams.originalMsgSender, + processParams.account, + processParams.username, + processParams.primitiveCustomParams, + ruleParams + ) ) ); } @@ -124,28 +152,32 @@ abstract contract RuleBasedNamespace is INamespace, RuleBasedPrimitive { ) internal { _processNamespaceRule( _encodeAndCallProcessCreation, - INamespaceRule.processCreation.selector, - originalMsgSender, - account, - username, - primitiveCustomParams, - rulesProcessingParams + ProcessParams({ + ruleSelector: INamespaceRule.processCreation.selector, + originalMsgSender: originalMsgSender, + account: account, + username: username, + primitiveCustomParams: primitiveCustomParams, + rulesProcessingParams: rulesProcessingParams + }) ); } function _encodeAndCallProcessRemoval( - address rule, - bytes32 configSalt, - address originalMsgSender, - address, /* account */ - string memory username, - KeyValue[] calldata primitiveCustomParams, - KeyValue[] memory ruleCustomParams + Rule memory rule, + ProcessParams memory processParams, + KeyValue[] memory ruleParams ) internal returns (bool, bytes memory) { - return rule.safecall( + return rule.ruleAddress.safecall( abi.encodeCall( INamespaceRule.processRemoval, - (configSalt, originalMsgSender, username, primitiveCustomParams, ruleCustomParams) + ( + rule.configSalt, + processParams.originalMsgSender, + processParams.username, + processParams.primitiveCustomParams, + ruleParams + ) ) ); } @@ -158,28 +190,33 @@ abstract contract RuleBasedNamespace is INamespace, RuleBasedPrimitive { ) internal { _processNamespaceRule( _encodeAndCallProcessRemoval, - INamespaceRule.processRemoval.selector, - originalMsgSender, - address(0), - username, - primitiveCustomParams, - rulesProcessingParams + ProcessParams({ + ruleSelector: INamespaceRule.processRemoval.selector, + originalMsgSender: originalMsgSender, + account: address(0), + username: username, + primitiveCustomParams: primitiveCustomParams, + rulesProcessingParams: rulesProcessingParams + }) ); } function _encodeAndCallProcessAssigning( - address rule, - bytes32 configSalt, - address originalMsgSender, - address account, - string memory username, - KeyValue[] calldata primitiveCustomParams, - KeyValue[] memory ruleCustomParams + Rule memory rule, + ProcessParams memory processParams, + KeyValue[] memory ruleParams ) internal returns (bool, bytes memory) { - return rule.safecall( + return rule.ruleAddress.safecall( abi.encodeCall( INamespaceRule.processAssigning, - (configSalt, originalMsgSender, account, username, primitiveCustomParams, ruleCustomParams) + ( + rule.configSalt, + processParams.originalMsgSender, + processParams.account, + processParams.username, + processParams.primitiveCustomParams, + ruleParams + ) ) ); } @@ -193,28 +230,33 @@ abstract contract RuleBasedNamespace is INamespace, RuleBasedPrimitive { ) internal { _processNamespaceRule( _encodeAndCallProcessAssigning, - INamespaceRule.processAssigning.selector, - originalMsgSender, - account, - username, - primitiveCustomParams, - rulesProcessingParams + ProcessParams({ + ruleSelector: INamespaceRule.processAssigning.selector, + originalMsgSender: originalMsgSender, + account: account, + username: username, + primitiveCustomParams: primitiveCustomParams, + rulesProcessingParams: rulesProcessingParams + }) ); } function _encodeAndCallProcessUnassigning( - address rule, - bytes32 configSalt, - address originalMsgSender, - address account, - string memory username, - KeyValue[] calldata primitiveCustomParams, - KeyValue[] memory ruleCustomParams + Rule memory rule, + ProcessParams memory processParams, + KeyValue[] memory ruleParams ) internal returns (bool, bytes memory) { - return rule.safecall( + return rule.ruleAddress.safecall( abi.encodeCall( INamespaceRule.processUnassigning, - (configSalt, originalMsgSender, account, username, primitiveCustomParams, ruleCustomParams) + ( + rule.configSalt, + processParams.originalMsgSender, + processParams.account, + processParams.username, + processParams.primitiveCustomParams, + ruleParams + ) ) ); } @@ -228,74 +270,51 @@ abstract contract RuleBasedNamespace is INamespace, RuleBasedPrimitive { ) internal { _processNamespaceRule( _encodeAndCallProcessUnassigning, - INamespaceRule.processUnassigning.selector, - originalMsgSender, - account, - username, - primitiveCustomParams, - rulesProcessingParams + ProcessParams({ + ruleSelector: INamespaceRule.processUnassigning.selector, + originalMsgSender: originalMsgSender, + account: account, + username: username, + primitiveCustomParams: primitiveCustomParams, + rulesProcessingParams: rulesProcessingParams + }) ); } + struct ProcessParams { + bytes4 ruleSelector; + address originalMsgSender; + address account; + string username; + KeyValue[] primitiveCustomParams; + RuleProcessingParams[] rulesProcessingParams; + } + function _processNamespaceRule( - function(address,bytes32,address,address,string memory,KeyValue[] calldata,KeyValue[] memory) internal returns (bool,bytes memory) - encodeAndCall, - bytes4 ruleSelector, - address originalMsgSender, - address account, - string memory username, - KeyValue[] calldata primitiveCustomParams, - RuleProcessingParams[] calldata rulesProcessingParams + function(Rule memory,ProcessParams memory,KeyValue[] memory) internal returns (bool,bytes memory) encodeAndCall, + ProcessParams memory processParams ) private { + Rule memory rule; + KeyValue[] memory ruleParams; // Check required rules (AND-combined rules) - for (uint256 i = 0; i < $namespaceRulesStorage().requiredRules[ruleSelector].length; i++) { - Rule memory rule = $namespaceRulesStorage().requiredRules[ruleSelector][i]; - for (uint256 j = 0; j < rulesProcessingParams.length; j++) { - KeyValue[] memory ruleParams = new KeyValue[](0); - if ( - rulesProcessingParams[j].ruleAddress == rule.ruleAddress - && rulesProcessingParams[j].configSalt == rule.configSalt - ) { - ruleParams = rulesProcessingParams[j].ruleParams; - } - (bool callNotReverted,) = encodeAndCall( - rule.ruleAddress, - rule.configSalt, - originalMsgSender, - account, - username, - primitiveCustomParams, - ruleParams - ); - require(callNotReverted, "Some required rule failed"); - } + for (uint256 i = 0; i < $namespaceRulesStorage().requiredRules[processParams.ruleSelector].length; i++) { + rule = $namespaceRulesStorage().requiredRules[processParams.ruleSelector][i]; + ruleParams = _getRuleParamsOrEmptyArray(rule, processParams.rulesProcessingParams); + (bool callSucceeded,) = encodeAndCall(rule, processParams, ruleParams); + require(callSucceeded, Errors.RequiredRuleReverted()); } // Check any-of rules (OR-combined rules) - for (uint256 i = 0; i < $namespaceRulesStorage().anyOfRules[ruleSelector].length; i++) { - Rule memory rule = $namespaceRulesStorage().anyOfRules[ruleSelector][i]; - for (uint256 j = 0; j < rulesProcessingParams.length; j++) { - KeyValue[] memory ruleParams = new KeyValue[](0); - if ( - rulesProcessingParams[j].ruleAddress == rule.ruleAddress - && rulesProcessingParams[j].configSalt == rule.configSalt - ) { - ruleParams = rulesProcessingParams[j].ruleParams; - } - (bool callNotReverted,) = encodeAndCall( - rule.ruleAddress, - rule.configSalt, - originalMsgSender, - account, - username, - primitiveCustomParams, - ruleParams - ); - if (callNotReverted) { - return; // If any of the OR-combined rules passed, it means they succeed and we can return - } + for (uint256 i = 0; i < $namespaceRulesStorage().anyOfRules[processParams.ruleSelector].length; i++) { + rule = $namespaceRulesStorage().anyOfRules[processParams.ruleSelector][i]; + ruleParams = _getRuleParamsOrEmptyArray(rule, processParams.rulesProcessingParams); + (bool callSucceeded,) = encodeAndCall(rule, processParams, ruleParams); + if (callSucceeded) { + return; // If any of the OR-combined rules passed, it means they succeed and we can return } } // If there are any-of rules and it reached this point, it means all of them failed. - require($namespaceRulesStorage().anyOfRules[ruleSelector].length == 0, "All of the any-of rules failed"); + require( + $namespaceRulesStorage().anyOfRules[processParams.ruleSelector].length == 0, Errors.AllAnyOfRulesReverted() + ); } } diff --git a/contracts/core/types/Errors.sol b/contracts/core/types/Errors.sol new file mode 100644 index 00000000..08b4fef6 --- /dev/null +++ b/contracts/core/types/Errors.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: UNLICENSED +// Copyright (C) 2024 Lens Labs. All Rights Reserved. +pragma solidity ^0.8.26; + +library Errors { + error AccessDenied(); + error AllAnyOfRulesReverted(); + error AlreadyExists(); + error AlreadyInitialized(); + error AutoUpgradeEnabled(); + error Banned(); + error Blocked(); + error CannotFollowAgain(); + error CannotHaveRules(); + error CannotStartWithThat(); + error ConfigureCallReverted(); + error Disabled(); + error DoesNotExist(); + error DuplicatedValue(); + error Expired(); + error Immutable(); + error InvalidConfigSalt(); + error InvalidMsgSender(); + error InvalidParameter(); + error InvalidSignature(); + error LimitReached(); + error Locked(); + error NonceUsed(); + error NotAContract(); + error NotAllowed(); + error NotAMember(); + error NotEnough(); + error NotFollowing(); + error NotFound(); + error NotImplemented(); + error RedundantStateChange(); + error RequiredRuleReverted(); + error RuleNotConfigured(); + error SelectorEnabledForDifferentRuleType(); + error ActionOnSelf(); + error SingleAnyOfRule(); + error UnexpectedContractImpl(); + error UnsupportedSelector(); + error Untrusted(); + error UsernameAssigned(); + error WrongSigner(); +} diff --git a/contracts/core/types/Events.sol b/contracts/core/types/Events.sol index 67299712..9b2ca52b 100644 --- a/contracts/core/types/Events.sol +++ b/contracts/core/types/Events.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.17; +pragma solidity ^0.8.26; library Events { event Lens_Contract_Deployed( diff --git a/contracts/core/types/Types.sol b/contracts/core/types/Types.sol index abd94892..b8cf4f33 100644 --- a/contracts/core/types/Types.sol +++ b/contracts/core/types/Types.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.17; +pragma solidity ^0.8.26; struct KeyValue { bytes32 key; diff --git a/contracts/core/upgradeability/Beacon.sol b/contracts/core/upgradeability/Beacon.sol index 962c7687..39619f31 100644 --- a/contracts/core/upgradeability/Beacon.sol +++ b/contracts/core/upgradeability/Beacon.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {Ownable} from "contracts/core/access/Ownable.sol"; import {IVersionedBeacon} from "contracts/core/interfaces/IVersionedBeacon.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; contract Beacon is Ownable, IVersionedBeacon { event ImplementationSetForVersion(uint256 indexed version, address indexed implementation); @@ -14,7 +15,7 @@ contract Beacon is Ownable, IVersionedBeacon { constructor(address owner, uint256 version, address initialImplementation) Ownable() { _transferOwnership(owner); - require(initialImplementation != address(0)); + require(initialImplementation != address(0), Errors.InvalidParameter()); _implementations[version] = initialImplementation; emit ImplementationSetForVersion(version, initialImplementation); _defaultVersion = version; @@ -27,20 +28,20 @@ contract Beacon is Ownable, IVersionedBeacon { function implementation(uint256 implementationVersion) external view override returns (address) { address implementationByVersion = _implementations[implementationVersion]; - require(implementationByVersion != address(0)); + require(implementationByVersion != address(0), Errors.InvalidParameter()); return implementationByVersion; } function setImplementationForVersion(uint256 version, address implementationToSet) external onlyOwner { if (_defaultVersion == version) { - require(implementationToSet != address(0)); + require(implementationToSet != address(0), Errors.InvalidParameter()); } _implementations[version] = implementationToSet; emit ImplementationSetForVersion(version, implementationToSet); } function setDefaultVersion(uint256 version) external onlyOwner { - require(_implementations[version] != address(0)); + require(_implementations[version] != address(0), Errors.InvalidParameter()); _defaultVersion = version; emit DefaultVersionSet(version); } diff --git a/contracts/core/upgradeability/BeaconProxy.sol b/contracts/core/upgradeability/BeaconProxy.sol index 06cfcbf0..b7764495 100644 --- a/contracts/core/upgradeability/BeaconProxy.sol +++ b/contracts/core/upgradeability/BeaconProxy.sol @@ -1,8 +1,9 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IVersionedBeacon} from "contracts/core/interfaces/IVersionedBeacon.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; contract BeaconProxy { event Upgraded(address indexed implementation); @@ -61,36 +62,36 @@ contract BeaconProxy { _fetchImplFromBeaconAndAutoUpgradeIfNeeded(); } - function changeProxyAdmin(address proxyAdmin) external { - require(msg.sender == $proxyAdmin().value); + function proxy__changeProxyAdmin(address proxyAdmin) external { + require(msg.sender == $proxyAdmin().value, Errors.InvalidMsgSender()); $proxyAdmin().value = proxyAdmin; emit AdminChanged(msg.sender, proxyAdmin); } - function optOutFromAutoUpgrade() external { - require(msg.sender == $proxyAdmin().value); + function proxy__optOutFromAutoUpgrade() external { + require(msg.sender == $proxyAdmin().value, Errors.InvalidMsgSender()); $autoUpgrade().value = false; emit AutoUpgradeChanged(false); } - function optInToAutoUpgrade() external { - require(msg.sender == $proxyAdmin().value); + function proxy__optInToAutoUpgrade() external { + require(msg.sender == $proxyAdmin().value, Errors.InvalidMsgSender()); $autoUpgrade().value = true; emit AutoUpgradeChanged(true); _fetchImplFromBeaconAndAutoUpgradeIfNeeded(); } - function setImplementation(address implementation) external { - require(msg.sender == $proxyAdmin().value); - require($autoUpgrade().value == false); + function proxy__setImplementation(address implementation) external { + require(msg.sender == $proxyAdmin().value, Errors.InvalidMsgSender()); + require($autoUpgrade().value == false, Errors.AutoUpgradeEnabled()); if (implementation != $implementation().value) { $implementation().value = implementation; emit Upgraded(implementation); } } - function setBeacon(address beacon) external { - require(msg.sender == $proxyAdmin().value); + function proxy__setBeacon(address beacon) external { + require(msg.sender == $proxyAdmin().value, Errors.InvalidMsgSender()); if (beacon != $beacon().value) { $beacon().value = beacon; emit BeaconUpgraded(beacon); @@ -100,8 +101,8 @@ contract BeaconProxy { } } - function triggerUpgradeToVersion(uint256 implementationVersion) external { - require(msg.sender == $proxyAdmin().value); + function proxy__triggerUpgradeToVersion(uint256 implementationVersion) external { + require(msg.sender == $proxyAdmin().value, Errors.InvalidMsgSender()); address implementationFromBeacon = IVersionedBeacon($beacon().value).implementation(implementationVersion); if (implementationFromBeacon != $implementation().value) { emit Upgraded(implementationFromBeacon); @@ -109,8 +110,8 @@ contract BeaconProxy { } } - function triggerUpgrade() external { - require(msg.sender == $proxyAdmin().value); + function proxy__triggerUpgrade() external { + require(msg.sender == $proxyAdmin().value, Errors.InvalidMsgSender()); _fetchImplFromBeaconAndAutoUpgradeIfNeeded(); } diff --git a/contracts/core/upgradeability/Initializable.sol b/contracts/core/upgradeability/Initializable.sol index a9ff385b..dd9e0f71 100644 --- a/contracts/core/upgradeability/Initializable.sol +++ b/contracts/core/upgradeability/Initializable.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; + +import {Errors} from "contracts/core/types/Errors.sol"; abstract contract Initializable { // Storage @@ -19,7 +21,7 @@ abstract contract Initializable { } modifier initializer() { - require(!$initializableStorage().initialized, "ALREADY_INITIALIZED"); + require(!$initializableStorage().initialized, Errors.AlreadyInitialized()); $initializableStorage().initialized = true; _; } diff --git a/contracts/core/upgradeability/Lock.sol b/contracts/core/upgradeability/Lock.sol index 591586e2..2fa9793d 100644 --- a/contracts/core/upgradeability/Lock.sol +++ b/contracts/core/upgradeability/Lock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {ILock} from "contracts/core/interfaces/ILock.sol"; import {Ownable} from "contracts/core/access/Ownable.sol"; diff --git a/contracts/core/upgradeability/ProxyAdmin.sol b/contracts/core/upgradeability/ProxyAdmin.sol index fa90f3f1..2022feb2 100644 --- a/contracts/core/upgradeability/ProxyAdmin.sol +++ b/contracts/core/upgradeability/ProxyAdmin.sol @@ -1,11 +1,12 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {ILock} from "contracts/core/interfaces/ILock.sol"; import {BeaconProxy} from "contracts/core/upgradeability/BeaconProxy.sol"; import {Ownable} from "contracts/core/access/Ownable.sol"; import {CallLib} from "contracts/core/libraries/CallLib.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; contract ProxyAdmin is Ownable { using CallLib for address; @@ -19,32 +20,23 @@ contract ProxyAdmin is Ownable { } function call(address to, uint256 value, bytes calldata data) external onlyOwner returns (bytes memory) { - bytes4 selector = bytes4(data[0]); + bytes4 selector = bytes4(data); if (LOCK.isLocked()) { // While the Proxy Admin is locked it: // - Cannot change Proxy Admin in the Proxy, only in the ProxyAdmin contract itself - require(selector != BeaconProxy.changeProxyAdmin.selector); + require(selector != BeaconProxy.proxy__changeProxyAdmin.selector, Errors.Locked()); // - Cannot change the Beacon in the Proxy - require(selector != BeaconProxy.setBeacon.selector); + require(selector != BeaconProxy.proxy__setBeacon.selector, Errors.Locked()); // - Cannot change the implementation in the Proxy - require(selector != BeaconProxy.setImplementation.selector); + require(selector != BeaconProxy.proxy__setImplementation.selector, Errors.Locked()); // - Cannot trigger an upgrade in the Proxy - require(selector != BeaconProxy.triggerUpgradeToVersion.selector); - require(selector != BeaconProxy.triggerUpgrade.selector); + require(selector != BeaconProxy.proxy__triggerUpgradeToVersion.selector, Errors.Locked()); + require(selector != BeaconProxy.proxy__triggerUpgrade.selector, Errors.Locked()); // - Cannot opt-out from auto-upgrade in the Proxy - require(selector != BeaconProxy.optOutFromAutoUpgrade.selector); + require(selector != BeaconProxy.proxy__optOutFromAutoUpgrade.selector, Errors.Locked()); // - Cannot opt-in to auto-upgrade in the Proxy - require(selector != BeaconProxy.optInToAutoUpgrade.selector); + require(selector != BeaconProxy.proxy__optInToAutoUpgrade.selector, Errors.Locked()); } - // Do the call - (bool success, bytes memory ret) = to.safecall(value, data); - if (!success) { - assembly { - // Equivalent to reverting with the returned error selector if the length is not zero. - let length := mload(ret) - if iszero(iszero(length)) { revert(add(ret, 32), length) } - } - } - return ret; + return to.handledsafecall(value, data); } } diff --git a/contracts/extensions/access/OwnerAdminOnlyAccessControl.sol b/contracts/extensions/access/OwnerAdminOnlyAccessControl.sol index 0fe94d82..c02e0fda 100644 --- a/contracts/extensions/access/OwnerAdminOnlyAccessControl.sol +++ b/contracts/extensions/access/OwnerAdminOnlyAccessControl.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {Events} from "contracts/core/types/Events.sol"; import {RoleBasedAccessControl} from "contracts/core/access/RoleBasedAccessControl.sol"; import {Access} from "contracts/core/interfaces/IRoleBasedAccessControl.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; contract OwnerAdminOnlyAccessControl is RoleBasedAccessControl { /// @custom:keccak lens.role.Admin @@ -17,7 +18,7 @@ contract OwnerAdminOnlyAccessControl is RoleBasedAccessControl { } function _beforeGrantingRole(address account, uint256 roleId) internal virtual override { - require(roleId == ADMIN_ROLE_ID, "You cannot grant other roles than ADMIN"); + require(roleId == ADMIN_ROLE_ID, Errors.InvalidParameter()); super._beforeGrantingRole(account, roleId); } @@ -27,7 +28,7 @@ contract OwnerAdminOnlyAccessControl is RoleBasedAccessControl { uint256, /*permissionId*/ Access /*access*/ ) internal virtual override { - revert(); + revert Errors.NotImplemented(); } function getType() external pure virtual override returns (bytes32) { diff --git a/contracts/extensions/access/PermissionlessAccessControl.sol b/contracts/extensions/access/PermissionlessAccessControl.sol index 459bb444..0ef6d7dd 100644 --- a/contracts/extensions/access/PermissionlessAccessControl.sol +++ b/contracts/extensions/access/PermissionlessAccessControl.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IAccessControl} from "contracts/core/interfaces/IAccessControl.sol"; diff --git a/contracts/extensions/account/Account.sol b/contracts/extensions/account/Account.sol index dfaffd2a..d1dfe72d 100644 --- a/contracts/extensions/account/Account.sol +++ b/contracts/extensions/account/Account.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.12; +pragma solidity ^0.8.26; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; @@ -12,8 +12,12 @@ import {ISource} from "contracts/core/interfaces/ISource.sol"; import {ExtraStorageBased} from "contracts/core/base/ExtraStorageBased.sol"; import {MetadataBased} from "contracts/core/base/MetadataBased.sol"; import {Initializable} from "contracts/core/upgradeability/Initializable.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; +import {CallLib} from "contracts/core/libraries/CallLib.sol"; contract Account is IAccount, Initializable, Ownable, IERC721Receiver, ExtraStorageBased, MetadataBased { + using CallLib for address; + // TODO: Think how long the timelock should be and should it be configurable uint256 constant SPENDING_TIMELOCK = 1 hours; @@ -74,9 +78,7 @@ contract Account is IAccount, Initializable, Ownable, IERC721Receiver, ExtraStor // TODO: Should we replace setMetadataURI with extraData? Cause here it looks like _setPrimitiveExtraDataByUser case function setMetadataURI(string calldata metadataURI, SourceStamp calldata sourceStamp) external override { if (msg.sender != owner()) { - require( - $storage().accountManagerPermissions[msg.sender].canSetMetadataURI, "No permissions to set metadata URI" - ); + require($storage().accountManagerPermissions[msg.sender].canSetMetadataURI, Errors.NotAllowed()); } if (sourceStamp.source != address(0)) { ISource(sourceStamp.source).validateSource(sourceStamp); @@ -92,10 +94,10 @@ contract Account is IAccount, Initializable, Ownable, IERC721Receiver, ExtraStor function allowNonOwnerSpending(bool allow) external onlyOwner { if (allow) { - require($storage().allowNonOwnerSpendingTimestamp == 0, "Non-Owner Spending Already Allowed"); + require($storage().allowNonOwnerSpendingTimestamp == 0, Errors.RedundantStateChange()); $storage().allowNonOwnerSpendingTimestamp = block.timestamp; } else { - require($storage().allowNonOwnerSpendingTimestamp > 0, "Non-Owner Spending Already Not Allowed"); + require($storage().allowNonOwnerSpendingTimestamp > 0, Errors.RedundantStateChange()); delete $storage().allowNonOwnerSpendingTimestamp; } emit Lens_Account_AllowNonOwnerSpending(allow, allow ? block.timestamp : 0); @@ -107,18 +109,17 @@ contract Account is IAccount, Initializable, Ownable, IERC721Receiver, ExtraStor onlyOwner { require( - !$storage().accountManagerPermissions[accountManager].canExecuteTransactions, - "Account manager already exists" + !$storage().accountManagerPermissions[accountManager].canExecuteTransactions, Errors.RedundantStateChange() ); - require(accountManager != owner(), "Cannot add owner as account manager"); - require(accountManager != address(0), "Cannot add zero address as account manager"); + require(accountManager != owner(), Errors.InvalidParameter()); + require(accountManager != address(0), Errors.InvalidParameter()); $storage().accountManagerPermissions[accountManager] = accountManagerPermissions; emit Lens_Account_AccountManagerAdded(accountManager, accountManagerPermissions); } function removeAccountManager(address accountManager) external override onlyOwner { require( - $storage().accountManagerPermissions[accountManager].canExecuteTransactions, "Account manager already exists" + $storage().accountManagerPermissions[accountManager].canExecuteTransactions, Errors.RedundantStateChange() ); delete $storage().accountManagerPermissions[accountManager]; emit Lens_Account_AccountManagerRemoved(accountManager); @@ -128,10 +129,8 @@ contract Account is IAccount, Initializable, Ownable, IERC721Receiver, ExtraStor address accountManager, AccountManagerPermissions calldata accountManagerPermissions ) external override onlyOwner { - require( - $storage().accountManagerPermissions[accountManager].canExecuteTransactions, "Account manager does not exist" - ); - require(accountManagerPermissions.canExecuteTransactions, "Cannot remove execution permissions"); + require($storage().accountManagerPermissions[accountManager].canExecuteTransactions, Errors.InvalidParameter()); + require(accountManagerPermissions.canExecuteTransactions, Errors.InvalidParameter()); $storage().accountManagerPermissions[accountManager] = accountManagerPermissions; emit Lens_Account_AccountManagerUpdated(accountManager, accountManagerPermissions); } @@ -147,38 +146,22 @@ contract Account is IAccount, Initializable, Ownable, IERC721Receiver, ExtraStor returns (bytes memory) { if (msg.sender != owner()) { - require( - $storage().accountManagerPermissions[msg.sender].canExecuteTransactions, - "No permissions to execute transactions" - ); + require($storage().accountManagerPermissions[msg.sender].canExecuteTransactions, Errors.NotAllowed()); if (value > 0) { - require( - $storage().accountManagerPermissions[msg.sender].canTransferNative, - "No permissions to transfer native tokens" - ); + require($storage().accountManagerPermissions[msg.sender].canTransferNative, Errors.NotAllowed()); } if (_isTransferRelatedSelector(bytes4(data[:4]))) { require( $storage().allowNonOwnerSpendingTimestamp > 0 && block.timestamp - $storage().allowNonOwnerSpendingTimestamp > SPENDING_TIMELOCK, - "Spender Lock ON: Non-owner spending not allowed" + Errors.NotAllowed() ); - require( - $storage().accountManagerPermissions[msg.sender].canTransferTokens, - "No permissions to transfer tokens" - ); - } - } - (bool success, bytes memory ret) = to.call{value: value}(data); - if (!success) { - assembly { - // Equivalent to reverting with the returned error selector if the length is not zero. - let length := mload(ret) - if iszero(iszero(length)) { revert(add(ret, 32), length) } + require($storage().accountManagerPermissions[msg.sender].canTransferTokens, Errors.NotAllowed()); } } + bytes memory returnData = to.handledcall(value, data); emit Lens_Account_TransactionExecuted(to, value, data, msg.sender); - return ret; + return returnData; } receive() external payable override {} diff --git a/contracts/extensions/account/IAccount.sol b/contracts/extensions/account/IAccount.sol index 7333c774..a0ec1331 100644 --- a/contracts/extensions/account/IAccount.sol +++ b/contracts/extensions/account/IAccount.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.17; +pragma solidity ^0.8.26; import {SourceStamp, KeyValue} from "contracts/core/types/Types.sol"; import {IMetadataBased} from "contracts/core/interfaces/IMetadataBased.sol"; diff --git a/contracts/extensions/actions/ActionHub.sol b/contracts/extensions/actions/ActionHub.sol index 54e7cb9c..229441a8 100644 --- a/contracts/extensions/actions/ActionHub.sol +++ b/contracts/extensions/actions/ActionHub.sol @@ -1,8 +1,9 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {KeyValue} from "contracts/core/types/Types.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; interface IPostAction { function configure(address originalMsgSender, address feed, uint256 postId, KeyValue[] calldata params) @@ -98,7 +99,7 @@ contract ActionHub { function signalUniversalPostAction(address action) external { bytes memory returnData = IPostAction(action).configure(address(0), address(0), 0, new KeyValue[](0)); - require(abi.decode(returnData, (bytes32)) == UNIVERSAL_ACTION_MAGIC_VALUE); + require(abi.decode(returnData, (bytes32)) == UNIVERSAL_ACTION_MAGIC_VALUE, Errors.UnexpectedContractImpl()); emit Lens_ActionHub_PostAction_Universal(action); } @@ -144,7 +145,7 @@ contract ActionHub { function signalUniversalAccountAction(address action) external { bytes memory returnData = IAccountAction(action).configure(address(0), address(0), new KeyValue[](0)); - require(abi.decode(returnData, (bytes32)) == UNIVERSAL_ACTION_MAGIC_VALUE); + require(abi.decode(returnData, (bytes32)) == UNIVERSAL_ACTION_MAGIC_VALUE, Errors.UnexpectedContractImpl()); emit Lens_ActionHub_AccountAction_Universal(action); } diff --git a/contracts/extensions/factories/AccessControlFactory.sol b/contracts/extensions/factories/AccessControlFactory.sol index a753ca10..2ec4658e 100644 --- a/contracts/extensions/factories/AccessControlFactory.sol +++ b/contracts/extensions/factories/AccessControlFactory.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IRoleBasedAccessControl} from "contracts/core/interfaces/IRoleBasedAccessControl.sol"; import {OwnerAdminOnlyAccessControl} from "contracts/extensions/access/OwnerAdminOnlyAccessControl.sol"; diff --git a/contracts/extensions/factories/AccountFactory.sol b/contracts/extensions/factories/AccountFactory.sol index b20e1938..5a959517 100644 --- a/contracts/extensions/factories/AccountFactory.sol +++ b/contracts/extensions/factories/AccountFactory.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {Account, AccountManagerPermissions} from "contracts/extensions/account/Account.sol"; import {KeyValue, SourceStamp} from "contracts/core/types/Types.sol"; diff --git a/contracts/extensions/factories/AppFactory.sol b/contracts/extensions/factories/AppFactory.sol index 7fe3d969..579af819 100644 --- a/contracts/extensions/factories/AppFactory.sol +++ b/contracts/extensions/factories/AppFactory.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IAccessControl} from "contracts/core/interfaces/IAccessControl.sol"; import {AppInitialProperties, App} from "contracts/extensions/primitives/app/App.sol"; diff --git a/contracts/extensions/factories/FeedFactory.sol b/contracts/extensions/factories/FeedFactory.sol index 77b2d678..67b74abe 100644 --- a/contracts/extensions/factories/FeedFactory.sol +++ b/contracts/extensions/factories/FeedFactory.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IAccessControl} from "contracts/core/interfaces/IAccessControl.sol"; import {Feed} from "contracts/core/primitives/feed/Feed.sol"; diff --git a/contracts/extensions/factories/GraphFactory.sol b/contracts/extensions/factories/GraphFactory.sol index bcdafde8..8548658a 100644 --- a/contracts/extensions/factories/GraphFactory.sol +++ b/contracts/extensions/factories/GraphFactory.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IAccessControl} from "contracts/core/interfaces/IAccessControl.sol"; import {Graph} from "contracts/core/primitives/graph/Graph.sol"; diff --git a/contracts/extensions/factories/GroupFactory.sol b/contracts/extensions/factories/GroupFactory.sol index 38fc76bf..b9583949 100644 --- a/contracts/extensions/factories/GroupFactory.sol +++ b/contracts/extensions/factories/GroupFactory.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IAccessControl} from "contracts/core/interfaces/IAccessControl.sol"; import {Group} from "contracts/core/primitives/group/Group.sol"; diff --git a/contracts/extensions/factories/LensFactory.sol b/contracts/extensions/factories/LensFactory.sol index 7b4f420e..50db4d41 100644 --- a/contracts/extensions/factories/LensFactory.sol +++ b/contracts/extensions/factories/LensFactory.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IRoleBasedAccessControl} from "contracts/core/interfaces/IRoleBasedAccessControl.sol"; import {IAccessControl} from "contracts/core/interfaces/IAccessControl.sol"; @@ -31,6 +31,7 @@ import {IGraphRule} from "contracts/core/interfaces/IGraphRule.sol"; import {PARAM__GROUP} from "contracts/rules/feed/GroupGatedFeedRule.sol"; import {AccessControlled} from "contracts/core/access/AccessControlled.sol"; import {IGroup} from "contracts/core/interfaces/IGroup.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; // TODO: Move this some place else or remove interface IOwnable { @@ -51,6 +52,25 @@ bytes32 constant DATA__GROUP_LINKED_FEED = 0xfec1c12508813d27a0104e0d1f0ad007b92 /// @custom:keccak lens.param.accessControl bytes32 constant PARAM__ACCESS_CONTROL = 0xcf3b0fab90208e4185bf857e0f943f6672abffb7d0898e0750beeeb991ae35fa; +struct CreateAccountParams { + string metadataURI; + address owner; + address[] accountManagers; + AccountManagerPermissions[] accountManagersPermissions; + SourceStamp accountCreationSourceStamp; + KeyValue[] accountExtraData; +} + +struct CreateUsernameParams { + string username; + KeyValue[] createUsernameCustomParams; + RuleProcessingParams[] createUsernameRuleProcessingParams; + KeyValue[] assignUsernameCustomParams; + RuleProcessingParams[] unassignAccountRuleProcessingParams; + RuleProcessingParams[] assignRuleProcessingParams; + KeyValue[] usernameExtraData; +} + contract LensFactory { AccessControlFactory internal immutable ACCESS_CONTROL_FACTORY; AccountFactory internal immutable ACCOUNT_FACTORY; @@ -88,111 +108,122 @@ contract LensFactory { // TODO: This function belongs to an App probably. function createAccountWithUsernameFree( - string calldata metadataURI, - address owner, - address[] calldata accountManagers, - AccountManagerPermissions[] calldata accountManagersPermissions, address namespacePrimitiveAddress, - string calldata username, - SourceStamp calldata accountCreationSourceStamp, - KeyValue[] calldata createUsernameCustomParams, - RuleProcessingParams[] calldata createUsernameRuleProcessingParams, - KeyValue[] calldata assignUsernameCustomParams, - RuleProcessingParams[] calldata unassignAccountRuleProcessingParams, - RuleProcessingParams[] calldata assignRuleProcessingParams, - KeyValue[] calldata accountExtraData, - KeyValue[] calldata usernameExtraData + CreateAccountParams calldata accountParams, + CreateUsernameParams calldata usernameParams ) external returns (address) { address account = ACCOUNT_FACTORY.deployAccount( address(this), - metadataURI, - accountManagers, - accountManagersPermissions, - accountCreationSourceStamp, - accountExtraData + accountParams.metadataURI, + accountParams.accountManagers, + accountParams.accountManagersPermissions, + accountParams.accountCreationSourceStamp, + accountParams.accountExtraData ); INamespace namespacePrimitive = INamespace(namespacePrimitiveAddress); bytes memory txData = abi.encodeCall( namespacePrimitive.createUsername, - (account, username, createUsernameCustomParams, createUsernameRuleProcessingParams, usernameExtraData) + ( + account, + usernameParams.username, + usernameParams.createUsernameCustomParams, + usernameParams.createUsernameRuleProcessingParams, + usernameParams.usernameExtraData + ) ); IAccount(payable(account)).executeTransaction(namespacePrimitiveAddress, uint256(0), txData); txData = abi.encodeCall( namespacePrimitive.assignUsername, ( account, - username, - assignUsernameCustomParams, - unassignAccountRuleProcessingParams, + usernameParams.username, + usernameParams.assignUsernameCustomParams, + usernameParams.unassignAccountRuleProcessingParams, new RuleProcessingParams[](0), - assignRuleProcessingParams + usernameParams.assignRuleProcessingParams ) ); IAccount(payable(account)).executeTransaction(namespacePrimitiveAddress, uint256(0), txData); - IOwnable(account).transferOwnership(owner); + IOwnable(account).transferOwnership(accountParams.owner); return account; } + struct CreateGroupWithFeedParams { + address group; + IRoleBasedAccessControl groupAccessControl; + IRoleBasedAccessControl feedAccessControl; + RuleChange[] modifiedFeedRules; + KeyValue[] feedExtraData; + } + function createGroupWithFeed( address owner, - address[] calldata admins, - string calldata groupMetadataURI, - RuleChange[] calldata groupRules, - KeyValue[] calldata groupExtraData, - string calldata feedMetadataURI, - RuleChange[] calldata feedRules, - KeyValue[] calldata feedExtraData + address[] memory admins, + string memory groupMetadataURI, + RuleChange[] memory groupRules, + KeyValue[] memory groupExtraData, + string memory feedMetadataURI, + RuleChange[] memory feedRules, + KeyValue[] memory feedExtraData ) external returns (address, address) { - IRoleBasedAccessControl groupAccessControl = _deployAccessControl(owner, admins); + CreateGroupWithFeedParams memory s; + s.feedExtraData = feedExtraData; + s.feedAccessControl = _deployAccessControl(owner, admins); - address group = GROUP_FACTORY.deployGroup( - groupMetadataURI, - TEMPORARY_ACCESS_CONTROL, - owner, - _injectRuleAccessControl(groupRules, address(groupAccessControl)), - groupExtraData - ); + { + s.groupAccessControl = _deployAccessControl(owner, admins); - RuleChange[] memory modifiedFeedRules = new RuleChange[](feedRules.length + 2); + s.group = GROUP_FACTORY.deployGroup( + groupMetadataURI, + TEMPORARY_ACCESS_CONTROL, + owner, + _injectRuleAccessControl(groupRules, address(s.groupAccessControl)), + groupExtraData + ); + } - RuleSelectorChange[] memory selectorChanges = new RuleSelectorChange[](1); - // Both rules only operate on IFeedRule.processCreatePost.selector (at least at the moment of writing this) - selectorChanges[0] = - RuleSelectorChange({ruleSelector: IFeedRule.processCreatePost.selector, isRequired: true, enabled: true}); + s.modifiedFeedRules = new RuleChange[](feedRules.length + 2); - modifiedFeedRules[0] = RuleChange({ - ruleAddress: ACCOUNT_BLOCKING_RULE, - configSalt: bytes32(0), - configurationChanges: RuleConfigurationChange({configure: true, ruleParams: new KeyValue[](0)}), - selectorChanges: selectorChanges - }); + { + RuleSelectorChange[] memory selectorChanges = new RuleSelectorChange[](1); + // Both rules only operate on IFeedRule.processCreatePost.selector (at least at the moment of writing this) + selectorChanges[0] = + RuleSelectorChange({ruleSelector: IFeedRule.processCreatePost.selector, isRequired: true, enabled: true}); - KeyValue[] memory groupGatedRuleParams = new KeyValue[](1); - groupGatedRuleParams[0] = KeyValue({key: PARAM__GROUP, value: abi.encode(group)}); + s.modifiedFeedRules[0] = RuleChange({ + ruleAddress: ACCOUNT_BLOCKING_RULE, + configSalt: bytes32(0), + configurationChanges: RuleConfigurationChange({configure: true, ruleParams: new KeyValue[](0)}), + selectorChanges: selectorChanges + }); - modifiedFeedRules[1] = RuleChange({ - ruleAddress: GROUP_GATED_FEED_RULE, - configSalt: bytes32(0), - configurationChanges: RuleConfigurationChange({configure: true, ruleParams: groupGatedRuleParams}), - selectorChanges: selectorChanges - }); + KeyValue[] memory groupGatedRuleParams = new KeyValue[](1); + groupGatedRuleParams[0] = KeyValue({key: PARAM__GROUP, value: abi.encode(s.group)}); - IRoleBasedAccessControl feedAccessControl = _deployAccessControl(owner, admins); + s.modifiedFeedRules[1] = RuleChange({ + ruleAddress: GROUP_GATED_FEED_RULE, + configSalt: bytes32(0), + configurationChanges: RuleConfigurationChange({configure: true, ruleParams: groupGatedRuleParams}), + selectorChanges: selectorChanges + }); + } - for (uint256 i = 0; i < feedRules.length; i++) { - require(feedRules[i].ruleAddress != ACCOUNT_BLOCKING_RULE, "ACCOUNT_BLOCKING_RULE WAS ALREADY PREPENDED"); - require(feedRules[i].ruleAddress != GROUP_GATED_FEED_RULE, "GroupGatedRule was already prepended"); - modifiedFeedRules[i + 2] = _injectRuleAccessControl(feedRules[i], address(feedAccessControl)); + { + for (uint256 i = 0; i < feedRules.length; i++) { + require(feedRules[i].ruleAddress != ACCOUNT_BLOCKING_RULE, Errors.DuplicatedValue()); + require(feedRules[i].ruleAddress != GROUP_GATED_FEED_RULE, Errors.DuplicatedValue()); + s.modifiedFeedRules[i + 2] = _injectRuleAccessControl(feedRules[i], address(s.feedAccessControl)); + } } address feed = - FEED_FACTORY.deployFeed(feedMetadataURI, feedAccessControl, owner, modifiedFeedRules, feedExtraData); + FEED_FACTORY.deployFeed(feedMetadataURI, s.feedAccessControl, owner, s.modifiedFeedRules, s.feedExtraData); KeyValue[] memory groupExtraDataWithFeed = new KeyValue[](1); groupExtraDataWithFeed[0] = KeyValue({key: DATA__GROUP_LINKED_FEED, value: abi.encode(feed)}); - IGroup(group).setExtraData(groupExtraDataWithFeed); - AccessControlled(group).setAccessControl(groupAccessControl); - return (group, feed); + IGroup(s.group).setExtraData(groupExtraDataWithFeed); + AccessControlled(s.group).setAccessControl(s.groupAccessControl); + return (s.group, feed); } function deployAccount( @@ -274,14 +305,14 @@ contract LensFactory { } function deployNamespace( - string calldata namespace, - string calldata metadataURI, + string memory namespace, + string memory metadataURI, address owner, - address[] calldata admins, + address[] memory admins, RuleChange[] calldata rules, KeyValue[] calldata extraData, - string calldata nftName, - string calldata nftSymbol + string memory nftName, + string memory nftSymbol ) external returns (address) { ITokenURIProvider tokenURIProvider = new LensUsernameTokenURIProvider(); IRoleBasedAccessControl accessControl = _deployAccessControl(owner, admins); @@ -298,7 +329,7 @@ contract LensFactory { ); } - function _deployAccessControl(address owner, address[] calldata admins) internal returns (IRoleBasedAccessControl) { + function _deployAccessControl(address owner, address[] memory admins) internal returns (IRoleBasedAccessControl) { return ACCESS_CONTROL_FACTORY.deployOwnerAdminOnlyAccessControl(owner, admins); } @@ -311,9 +342,9 @@ contract LensFactory { if (rule.configurationChanges.configure) { for (uint256 i = 0; i < rule.configurationChanges.ruleParams.length; i++) { if (rule.configurationChanges.ruleParams[i].key == PARAM__ACCESS_CONTROL) { - require(!found); + require(!found, Errors.DuplicatedValue()); found = true; - require(rule.configurationChanges.ruleParams[i].value.length == 0); + require(rule.configurationChanges.ruleParams[i].value.length == 0, Errors.InvalidParameter()); rule.configurationChanges.ruleParams[i].value = abi.encode(accessControl); } } @@ -321,7 +352,7 @@ contract LensFactory { return rule; } - function _injectRuleAccessControl(RuleChange[] calldata rules, address accessControl) + function _injectRuleAccessControl(RuleChange[] memory rules, address accessControl) internal pure returns (RuleChange[] memory) @@ -333,7 +364,7 @@ contract LensFactory { return modifiedRules; } - function _prepareRules(RuleChange[] calldata rules, bytes4 ruleSelector, address accessControl) + function _prepareRules(RuleChange[] memory rules, bytes4 ruleSelector, address accessControl) internal view returns (RuleChange[] memory) @@ -348,7 +379,7 @@ contract LensFactory { selectorChanges: selectorChanges }); for (uint256 i = 0; i < rules.length; i++) { - require(rules[i].ruleAddress != ACCOUNT_BLOCKING_RULE, "ACCOUNT_BLOCKING_RULE WAS ALREADY PREPENDED"); + require(rules[i].ruleAddress != ACCOUNT_BLOCKING_RULE, Errors.DuplicatedValue()); modifiedRules[i + 1] = _injectRuleAccessControl(rules[i], accessControl); } return modifiedRules; diff --git a/contracts/extensions/factories/NamespaceFactory.sol b/contracts/extensions/factories/NamespaceFactory.sol index c3eec2d5..c55fac17 100644 --- a/contracts/extensions/factories/NamespaceFactory.sol +++ b/contracts/extensions/factories/NamespaceFactory.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IAccessControl} from "contracts/core/interfaces/IAccessControl.sol"; import {Namespace} from "contracts/core/primitives/namespace/Namespace.sol"; diff --git a/contracts/extensions/factories/PrimitiveFactory.sol b/contracts/extensions/factories/PrimitiveFactory.sol index f846b224..ea6d8392 100644 --- a/contracts/extensions/factories/PrimitiveFactory.sol +++ b/contracts/extensions/factories/PrimitiveFactory.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IAccessControl} from "contracts/core/interfaces/IAccessControl.sol"; import {PermissionlessAccessControl} from "contracts/extensions/access/PermissionlessAccessControl.sol"; diff --git a/contracts/extensions/primitives/app/App.sol b/contracts/extensions/primitives/app/App.sol index 7cf86ed6..5b135e54 100644 --- a/contracts/extensions/primitives/app/App.sol +++ b/contracts/extensions/primitives/app/App.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IAccessControl} from "contracts/core/interfaces/IAccessControl.sol"; import {IApp} from "contracts/extensions/primitives/app/IApp.sol"; diff --git a/contracts/extensions/primitives/app/AppCore.sol b/contracts/extensions/primitives/app/AppCore.sol index a8acae33..8d8e5755 100644 --- a/contracts/extensions/primitives/app/AppCore.sol +++ b/contracts/extensions/primitives/app/AppCore.sol @@ -1,8 +1,9 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.17; +pragma solidity ^0.8.26; import "contracts/core/libraries/ExtraDataLib.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; struct ArrayStorageHelper { uint8 index; @@ -50,8 +51,8 @@ library AppCore { function _add(address element, address[] storage array, mapping(address => ArrayStorageHelper) storage arrayHelper) internal { - require(element != address(0), "INVALID_ELEMENT"); - require(!arrayHelper[element].isSet, "ALREADY_ADDED"); + require(element != address(0), Errors.InvalidParameter()); + require(!arrayHelper[element].isSet, Errors.RedundantStateChange()); array.push(element); arrayHelper[element] = ArrayStorageHelper({index: uint8(array.length - 1), isSet: true}); } @@ -61,7 +62,7 @@ library AppCore { address[] storage array, mapping(address => ArrayStorageHelper) storage arrayHelper ) internal { - require(arrayHelper[element].isSet, "NOT_FOUND"); + require(arrayHelper[element].isSet, Errors.NotFound()); uint256 index = arrayHelper[element].index; array[index] = array[array.length - 1]; arrayHelper[array[index]].index = uint8(index); @@ -83,7 +84,7 @@ library AppCore { bool wasAValuePreviouslySet = $storage().defaultGraph != address(0); if (graph != address(0)) { // address(0) allowed as a way to remove the default graph - require($storage().graphStorageHelper[graph].isSet, "NOT_FOUND"); + require($storage().graphStorageHelper[graph].isSet, Errors.NotFound()); } $storage().defaultGraph = graph; return wasAValuePreviouslySet; @@ -103,7 +104,7 @@ library AppCore { bool wasAValuePreviouslySet = $storage().defaultFeed != address(0); if (feed != address(0)) { // address(0) allowed as a way to remove the default feed - require($storage().feedStorageHelper[feed].isSet, "NOT_FOUND"); + require($storage().feedStorageHelper[feed].isSet, Errors.NotFound()); } $storage().defaultFeed = feed; return wasAValuePreviouslySet; @@ -127,7 +128,7 @@ library AppCore { bool wasAValuePreviouslySet = $storage().defaultNamespace != address(0); if (namespace != address(0)) { // address(0) allowed as a way to remove the default namespace - require($storage().namespaceStorageHelper[namespace].isSet, "NOT_FOUND"); + require($storage().namespaceStorageHelper[namespace].isSet, Errors.NotFound()); } $storage().defaultNamespace = namespace; return wasAValuePreviouslySet; @@ -146,7 +147,7 @@ library AppCore { function _setDefaultGroup(address group) internal { if (group != address(0)) { // address(0) allowed as a way to remove the default group - require($storage().groupStorageHelper[group].isSet, "NOT_FOUND"); + require($storage().groupStorageHelper[group].isSet, Errors.NotFound()); } $storage().defaultGroup = group; } @@ -165,7 +166,7 @@ library AppCore { bool wasAValuePreviouslySet = $storage().defaultPaymaster != address(0); if (paymaster != address(0)) { // address(0) allowed as a way to remove the default paymaster - require($storage().paymasterStorageHelper[paymaster].isSet, "NOT_FOUND"); + require($storage().paymasterStorageHelper[paymaster].isSet, Errors.NotFound()); } $storage().defaultPaymaster = paymaster; return wasAValuePreviouslySet; diff --git a/contracts/extensions/primitives/app/IApp.sol b/contracts/extensions/primitives/app/IApp.sol index c8c17949..a7f7bcb6 100644 --- a/contracts/extensions/primitives/app/IApp.sol +++ b/contracts/extensions/primitives/app/IApp.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IMetadataBased} from "contracts/core/interfaces/IMetadataBased.sol"; import {KeyValue} from "contracts/core/types/Types.sol"; diff --git a/contracts/extensions/registries/CurrencyRegistry_MockEvents.sol b/contracts/extensions/registries/CurrencyRegistry_MockEvents.sol index 157d6b13..610b169d 100644 --- a/contracts/extensions/registries/CurrencyRegistry_MockEvents.sol +++ b/contracts/extensions/registries/CurrencyRegistry_MockEvents.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; contract CurrencyRegistry { event Lens_Currency_Registered(address indexed currency, string name, string symbol, uint8 decimals); diff --git a/contracts/extensions/registries/PostActionsRegistry_MockEvents.sol b/contracts/extensions/registries/PostActionsRegistry_MockEvents.sol index 7be9a9ea..86ed1cea 100644 --- a/contracts/extensions/registries/PostActionsRegistry_MockEvents.sol +++ b/contracts/extensions/registries/PostActionsRegistry_MockEvents.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; contract PostActionsRegistry { // This is emitted when a DEV signals they developed a new PostAction that someone can use diff --git a/contracts/migration/MigrationFeed.sol b/contracts/migration/MigrationFeed.sol index 17b8fd6c..76947932 100644 --- a/contracts/migration/MigrationFeed.sol +++ b/contracts/migration/MigrationFeed.sol @@ -1,56 +1,56 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {KeyValue, RuleProcessingParams} from "contracts/core/types/Types.sol"; import {CreatePostParams} from "contracts/core/interfaces/IFeed.sol"; import {FeedCore as Core, PostStorage} from "contracts/core/primitives/feed/FeedCore.sol"; import {Feed} from "contracts/core/primitives/feed/Feed.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; + +struct PostCreationParams { + uint256 authorPostSequentialId; + uint80 creationTimestamp; + address source; +} contract MigrationFeed is Feed { function createPost( - CreatePostParams calldata postParams, - KeyValue[] calldata customParams, - RuleProcessingParams[] calldata feedRulesParams, - RuleProcessingParams[] calldata rootPostRulesParams, - RuleProcessingParams[] calldata quotedPostRulesParams + CreatePostParams memory postParams, + KeyValue[] memory customParams, + RuleProcessingParams[] memory feedRulesParams, + RuleProcessingParams[] memory rootPostRulesParams, + RuleProcessingParams[] memory quotedPostRulesParams ) external override returns (uint256) { - ( - uint256 postId, - uint256 rootPostId, - uint256 postSequentialId, - uint256 authorPostSequentialId, - uint80 creationTimestamp, - address source - ) = abi.decode(customParams[0].value, (uint256, uint256, uint256, uint256, uint80, address)); - _createPost(postParams, postId, rootPostId, postSequentialId, authorPostSequentialId, creationTimestamp); + PostCreationParams memory postCreationParams = abi.decode(customParams[0].value, (PostCreationParams)); + (uint256 postId, uint256 rootPostId) = + _createPost(postParams, postCreationParams.authorPostSequentialId, postCreationParams.creationTimestamp); - if (customParams.length > 1 && abi.decode(customParams[1].value, (bool))) { - // If customParams[1] is present, it must be an ABI-encoded bool representing `forceChecks` - _forceChecks(postId, rootPostId, postParams); - } - - if (source != address(0)) { + if (postCreationParams.source != address(0)) { // Trust the migrator, no source verification - _setPrimitiveInternalExtraDataForEntity(postId, KeyValue(DATA__SOURCE, abi.encode(source))); - _setPrimitiveInternalExtraDataForEntity(postId, KeyValue(DATA__LAST_UPDATED_SOURCE, abi.encode(source))); + _setPrimitiveInternalExtraDataForEntity( + postId, KeyValue(DATA__SOURCE, abi.encode(postCreationParams.source)) + ); + _setPrimitiveInternalExtraDataForEntity( + postId, KeyValue(DATA__LAST_UPDATED_SOURCE, abi.encode(postCreationParams.source)) + ); } emit Lens_Feed_PostCreated( postId, postParams.author, - authorPostSequentialId, + postCreationParams.authorPostSequentialId, rootPostId, postParams, customParams, feedRulesParams, rootPostRulesParams, quotedPostRulesParams, - source + postCreationParams.source ); for (uint256 i = 0; i < postParams.extraData.length; i++) { - _setEntityExtraData(postId, postParams.extraData[i]); + _setExtraData(postParams.author, postId, postParams.extraData[i]); emit Lens_Feed_Post_ExtraDataAdded( postId, postParams.extraData[i].key, postParams.extraData[i].value, postParams.extraData[i].value ); @@ -59,46 +59,47 @@ contract MigrationFeed is Feed { } // Overriding the FeedCore - function _createPost( - CreatePostParams calldata postParams, - uint256 postId, - uint256 rootPostId, - uint256 postSequentialId, - uint256 authorPostSequentialId, - uint80 creationTimestamp - ) internal { - Core.$storage().postCount = postSequentialId; - Core.$storage().authorPostCount[postParams.author] = authorPostSequentialId; + function _createPost(CreatePostParams memory postParams, uint256 authorPostSequentialId, uint80 creationTimestamp) + internal + returns (uint256, uint256) + { + require(authorPostSequentialId != 0, Errors.InvalidParameter()); + require(creationTimestamp != 0, Errors.InvalidParameter()); + + uint256 postSequentialId = Core.$storage().postCount++; + Core.$storage().authorPostCount[postParams.author]++; + uint256 postId = Core._generatePostId(postParams.author, authorPostSequentialId); PostStorage storage _newPost = Core.$storage().posts[postId]; + _newPost.author = postParams.author; _newPost.authorPostSequentialId = authorPostSequentialId; _newPost.postSequentialId = postSequentialId; _newPost.contentURI = postParams.contentURI; - _newPost.quotedPostId = postParams.quotedPostId; - _newPost.repliedPostId = postParams.repliedPostId; - _newPost.repostedPostId = postParams.repostedPostId; - _newPost.rootPostId = rootPostId; - _newPost.creationTimestamp = creationTimestamp; - _newPost.lastUpdatedTimestamp = creationTimestamp; - } - function _forceChecks(uint256 postId, uint256 rootPostId, CreatePostParams calldata postParams) internal view { - // TODO: Check if the rootPostId == postId case (not a reply, not a repost) - if (rootPostId != postId) { - require(Core._postExists(rootPostId)); - } + uint256 rootPostId = postId; + if (postParams.quotedPostId != 0) { - require(Core._postExists(postParams.quotedPostId)); + require(Core._postExists(postParams.quotedPostId), Errors.DoesNotExist()); + _newPost.quotedPostId = postParams.quotedPostId; } if (postParams.repliedPostId != 0) { - require(Core._postExists(postParams.repliedPostId)); - require(rootPostId == Core.$storage().posts[postParams.repliedPostId].rootPostId); + require(Core._postExists(postParams.repliedPostId), Errors.DoesNotExist()); + _newPost.repliedPostId = postParams.repliedPostId; + rootPostId = Core.$storage().posts[postParams.repliedPostId].rootPostId; } if (postParams.repostedPostId != 0) { - require(Core._postExists(postParams.repostedPostId)); - require(postParams.quotedPostId == 0 && postParams.repliedPostId == 0); - require(rootPostId == Core.$storage().posts[postParams.repostedPostId].rootPostId); - require(bytes(postParams.contentURI).length == 0, "REPOST_CANNOT_HAVE_CONTENT"); + require(Core._postExists(postParams.repostedPostId), Errors.DoesNotExist()); + _newPost.repostedPostId = postParams.repostedPostId; + rootPostId = Core.$storage().posts[postParams.repostedPostId].rootPostId; + require(postParams.quotedPostId == 0 && postParams.repliedPostId == 0, Errors.InvalidParameter()); + require(bytes(postParams.contentURI).length == 0, Errors.InvalidParameter()); } + if (rootPostId != postId) { + require(Core._postExists(rootPostId), Errors.DoesNotExist()); + } + _newPost.rootPostId = rootPostId; + _newPost.creationTimestamp = creationTimestamp; + _newPost.lastUpdatedTimestamp = creationTimestamp; + return (postId, rootPostId); } } diff --git a/contracts/migration/MigrationGraph.sol b/contracts/migration/MigrationGraph.sol index a8d1436c..acaadd91 100644 --- a/contracts/migration/MigrationGraph.sol +++ b/contracts/migration/MigrationGraph.sol @@ -1,11 +1,13 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {GraphCore as Core} from "contracts/core/primitives/graph/GraphCore.sol"; import {Graph} from "contracts/core/primitives/graph/Graph.sol"; import {RuleProcessingParams, KeyValue} from "contracts/core/types/Types.sol"; import {IAccessControl} from "contracts/core/interfaces/IAccessControl.sol"; +import {Follow} from "contracts/core/interfaces/IGraph.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; /** * Special Graph implementation to allow data migrations from Lens V2 to Lens V3 @@ -20,7 +22,7 @@ contract MigrationGraph is Graph { KeyValue[] calldata extraData ) external override returns (uint256) { (uint256 followId, uint256 timestamp) = abi.decode(customParams[0].value, (uint256, uint256)); - Core._follow(followerAccount, accountToFollow, followId, timestamp); + _followWithoutChecks(followerAccount, accountToFollow, followId, timestamp); emit Lens_Graph_Followed( followerAccount, accountToFollow, @@ -33,4 +35,19 @@ contract MigrationGraph is Graph { ); return followId; } + + function _followWithoutChecks(address followerAccount, address accountToFollow, uint256 followId, uint256 timestamp) + internal + { + require(followerAccount != accountToFollow, Errors.ActionOnSelf()); + require(followId != 0, Errors.InvalidParameter()); + require(followerAccount != address(0), Errors.InvalidParameter()); + require(accountToFollow != address(0), Errors.InvalidParameter()); + require(Core.$storage().follows[followerAccount][accountToFollow].id == 0, Errors.CannotFollowAgain()); + require(Core.$storage().followers[accountToFollow][followId] == address(0), Errors.AlreadyExists()); + Core.$storage().follows[followerAccount][accountToFollow] = Follow({id: followId, timestamp: timestamp}); + Core.$storage().followers[accountToFollow][followId] = followerAccount; + Core.$storage().followersCount[accountToFollow]++; + Core.$storage().followingCount[followerAccount]++; + } } diff --git a/contracts/rules/base/AccountBlockingRule.sol b/contracts/rules/base/AccountBlockingRule.sol index f106be40..6a53a1d1 100644 --- a/contracts/rules/base/AccountBlockingRule.sol +++ b/contracts/rules/base/AccountBlockingRule.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.17; +pragma solidity ^0.8.26; import {IFeedRule} from "contracts/core/interfaces/IFeedRule.sol"; import {IGraphRule} from "contracts/core/interfaces/IGraphRule.sol"; @@ -8,6 +8,7 @@ import {CreatePostParams, EditPostParams} from "contracts/core/interfaces/IFeed. import {KeyValue, RuleChange} from "contracts/core/types/Types.sol"; import {IFeed} from "contracts/core/interfaces/IFeed.sol"; import {MetadataBased} from "contracts/core/base/MetadataBased.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; contract AccountBlockingRule is IFeedRule, IGraphRule, MetadataBased { event Lens_AccountBlocking_AccountBlocked(address indexed source, address indexed target, uint256 timestamp); @@ -32,13 +33,13 @@ contract AccountBlockingRule is IFeedRule, IGraphRule, MetadataBased { {} function blockUser(address source, address target) external { - require(msg.sender == source, "Only the source can block a user"); - require(source != target, "Cannot block self"); + require(msg.sender == source, Errors.InvalidMsgSender()); + require(source != target, Errors.ActionOnSelf()); accountBlocks[source][target] = block.timestamp; } function unblockUser(address source, address target) external { - require(msg.sender == source, "Only the source can unblock a user"); + require(msg.sender == source, Errors.InvalidMsgSender()); accountBlocks[msg.sender][target] = 0; } @@ -55,10 +56,10 @@ contract AccountBlockingRule is IFeedRule, IGraphRule, MetadataBased { uint256 rootPostId = IFeed(msg.sender).getPost(postId).rootPostId; address rootAuthor = IFeed(msg.sender).getPostAuthor(rootPostId); if (_isBlocked({source: repliedToAuthor, blockTarget: author})) { - revert("User is blocked from replying to this user"); + revert Errors.Blocked(); } if (_isBlocked({source: rootAuthor, blockTarget: author})) { - revert("User is blocked from commenting on this author's posts"); + revert Errors.Blocked(); } } } @@ -72,7 +73,7 @@ contract AccountBlockingRule is IFeedRule, IGraphRule, MetadataBased { KeyValue[] calldata /* ruleExecutionParams */ ) external view { if (_isBlocked({source: accountToFollow, blockTarget: followerAccount})) { - revert("User is blocked from following this user"); + revert Errors.Blocked(); } } @@ -93,16 +94,16 @@ contract AccountBlockingRule is IFeedRule, IGraphRule, MetadataBased { KeyValue[] calldata, /* primitiveCustomParams */ KeyValue[] calldata /* ruleExecutionParams */ ) external pure { - revert(); + revert Errors.NotImplemented(); } - function processRemovePost( + function processDeletePost( bytes32, /* configSalt */ uint256, /* postId */ KeyValue[] calldata, /* primitiveCustomParams */ KeyValue[] calldata /* ruleExecutionParams */ ) external pure { - revert(); + revert Errors.NotImplemented(); } function processPostRuleChanges( @@ -111,7 +112,7 @@ contract AccountBlockingRule is IFeedRule, IGraphRule, MetadataBased { RuleChange[] calldata, /* ruleChanges */ KeyValue[] calldata /* ruleExecutionParams */ ) external pure { - revert(); + revert Errors.NotImplemented(); } function processUnfollow( @@ -122,7 +123,7 @@ contract AccountBlockingRule is IFeedRule, IGraphRule, MetadataBased { KeyValue[] calldata, /* primitiveCustomParams */ KeyValue[] calldata /* ruleExecutionParams */ ) external pure { - revert(); + revert Errors.NotImplemented(); } function processFollowRuleChanges( @@ -131,6 +132,6 @@ contract AccountBlockingRule is IFeedRule, IGraphRule, MetadataBased { RuleChange[] calldata, /* ruleChanges */ KeyValue[] calldata /* ruleExecutionParams */ ) external pure { - revert(); + revert Errors.NotImplemented(); } } diff --git a/contracts/rules/base/RestrictedSignersRule.sol b/contracts/rules/base/RestrictedSignersRule.sol index 12b2f9b1..349f2aed 100644 --- a/contracts/rules/base/RestrictedSignersRule.sol +++ b/contracts/rules/base/RestrictedSignersRule.sol @@ -1,13 +1,14 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {EIP712EncodingLib} from "contracts/core/libraries/EIP712EncodingLib.sol"; import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; import {KeyValue} from "contracts/core/types/Types.sol"; import {MetadataBased} from "contracts/core/base/MetadataBased.sol"; -// Move to types +import {Errors} from "contracts/core/types/Errors.sol"; +// Move to types struct EIP712Signature { address signer; uint8 v; @@ -81,12 +82,12 @@ abstract contract RestrictedSignersRule is MetadataBased { } function _configure(bytes32 configSalt, KeyValue[] calldata ruleParams) internal virtual { - require(ruleParams.length > 0); - require(ruleParams[0].key == PARAM__RESTRICTED_SIGNERS); + require(ruleParams.length > 0, Errors.InvalidParameter()); + require(ruleParams[0].key == PARAM__RESTRICTED_SIGNERS, Errors.InvalidParameter()); (address[] memory signers, string[] memory labels, bool[] memory isWhitelisted) = abi.decode(ruleParams[0].value, (address[], string[], bool[])); - require(signers.length == isWhitelisted.length); - require(signers.length == labels.length); + require(signers.length == isWhitelisted.length, Errors.InvalidParameter()); + require(signers.length == labels.length, Errors.InvalidParameter()); for (uint256 i = 0; i < signers.length; i++) { bool wasWhitelisted = $rulesStorage(msg.sender, configSalt).isWhitelistedSigner[signers[i]]; if (wasWhitelisted == isWhitelisted[i]) { @@ -115,15 +116,15 @@ abstract contract RestrictedSignersRule is MetadataBased { RestrictedSignerMessage memory message = RestrictedSignerMessage(functionSelector, abiEncodedFunctionParams, signature.nonce, signature.deadline); if (block.timestamp > signature.deadline) { - revert("Errors.SignatureExpired()"); + revert Errors.Expired(); } if ($rulesStorage(msg.sender, configSalt).wasSignerNonceUsed[signature.signer][signature.nonce]) { - revert("Errors.SignatureNonceUsed()"); + revert Errors.NonceUsed(); } $rulesStorage(msg.sender, configSalt).wasSignerNonceUsed[signature.signer][signature.nonce] = true; emit Lens_RestrictedSignersRule_SignerNonceUsed(signature.signer, signature.nonce); if (!$rulesStorage(msg.sender, configSalt).isWhitelistedSigner[signature.signer]) { - revert("Errors.SignerNotWhitelisted()"); + revert Errors.WrongSigner(); } bytes32 hashStruct = _calculateMessageHashStruct(message); bytes32 digest = _calculateDigest(hashStruct); @@ -160,18 +161,18 @@ abstract contract RestrictedSignersRule is MetadataBased { function _validateRecoveredAddress(bytes32 digest, EIP712Signature memory signature) private view { if (block.timestamp > signature.deadline) { - revert("Errors.SignatureExpired()"); + revert Errors.Expired(); } // If the expected address is a contract, check the signature there. if (signature.signer.code.length != 0) { bytes memory concatenatedSig = abi.encodePacked(signature.r, signature.s, signature.v); if (IERC1271(signature.signer).isValidSignature(digest, concatenatedSig) != EIP1271_MAGIC_VALUE) { - revert("Errors.SignatureInvalid()"); + revert Errors.InvalidSignature(); } } else { address recoveredAddress = ecrecover(digest, signature.v, signature.r, signature.s); if (recoveredAddress == address(0) || recoveredAddress != signature.signer) { - revert("Errors.SignatureInvalid()"); + revert Errors.WrongSigner(); } } } diff --git a/contracts/rules/base/SimplePaymentRule.sol b/contracts/rules/base/SimplePaymentRule.sol index dd94f830..2f133383 100644 --- a/contracts/rules/base/SimplePaymentRule.sol +++ b/contracts/rules/base/SimplePaymentRule.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {MetadataBased} from "contracts/core/base/MetadataBased.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; abstract contract SimplePaymentRule is MetadataBased { using SafeERC20 for IERC20; @@ -43,7 +44,7 @@ abstract contract SimplePaymentRule is MetadataBased { } function _validatePaymentConfiguration(PaymentConfiguration memory configuration) internal view virtual { - require(configuration.amount > 0, "Errors.CannotSetZeroAmount()"); + require(configuration.amount > 0, Errors.InvalidParameter()); // Expects token to support ERC-20 interface, we call balanceOf and expect it to not revert IERC20(configuration.token).balanceOf(address(this)); } @@ -53,11 +54,11 @@ abstract contract SimplePaymentRule is MetadataBased { PaymentConfiguration memory expectedConfiguration, address payer ) internal view virtual { - require(configuration.token == expectedConfiguration.token, "Errors.UnexpectedToken()"); - require(configuration.amount == expectedConfiguration.amount, "Errors.UnexpectedAmount()"); - require(configuration.recipient == expectedConfiguration.recipient, "Errors.UnexpectedRecipient()"); + require(configuration.token == expectedConfiguration.token, Errors.InvalidParameter()); + require(configuration.amount == expectedConfiguration.amount, Errors.InvalidParameter()); + require(configuration.recipient == expectedConfiguration.recipient, Errors.InvalidParameter()); // Requires payer to trust the msg.sender, which is acting as the primitive - require(_isTrusted[payer][msg.sender]); + require(_isTrusted[payer][msg.sender], Errors.Untrusted()); } function _processPayment( diff --git a/contracts/rules/base/TokenGatedRule.sol b/contracts/rules/base/TokenGatedRule.sol index 861835bc..f8b1aab7 100644 --- a/contracts/rules/base/TokenGatedRule.sol +++ b/contracts/rules/base/TokenGatedRule.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; import {MetadataBased} from "contracts/core/base/MetadataBased.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; interface IToken { /** @@ -38,7 +39,7 @@ abstract contract TokenGatedRule is MetadataBased { } function _validateTokenGateConfiguration(TokenGateConfiguration memory configuration) internal view { - require(configuration.amount > 0, "Errors.CannotSetZeroAmount()"); + require(configuration.amount > 0, Errors.InvalidParameter()); if (configuration.tokenStandard == ERC20 || configuration.tokenStandard == ERC721) { // Expects token to support ERC-20/ERC-721 balanceOf by not reverting IToken(configuration.token).balanceOf(address(this)); @@ -46,12 +47,12 @@ abstract contract TokenGatedRule is MetadataBased { // Expects token to support ERC-1155 balanceOf by not reverting IERC1155(configuration.token).balanceOf(address(this), configuration.typeId); } else { - revert("Errors.InvalidTokenStandard()"); + revert Errors.InvalidParameter(); } } function _validateTokenBalance(TokenGateConfiguration memory configuration, address owner) internal view { - require(_checkTokenBalance(configuration, owner), "Errors.InsufficientTokenBalance()"); + require(_checkTokenBalance(configuration, owner), Errors.NotEnough()); } function _checkTokenBalance(TokenGateConfiguration memory configuration, address owner) diff --git a/contracts/rules/feed/GroupGatedFeedRule.sol b/contracts/rules/feed/GroupGatedFeedRule.sol index c489acff..fd15a001 100644 --- a/contracts/rules/feed/GroupGatedFeedRule.sol +++ b/contracts/rules/feed/GroupGatedFeedRule.sol @@ -1,12 +1,13 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {CreatePostParams, EditPostParams} from "contracts/core/interfaces/IFeed.sol"; import {IFeedRule} from "contracts/core/interfaces/IFeedRule.sol"; import {IGroup} from "contracts/core/interfaces/IGroup.sol"; import {KeyValue, RuleChange} from "contracts/core/types/Types.sol"; import {MetadataBased} from "contracts/core/base/MetadataBased.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; /// @custom:keccak lens.param.group bytes32 constant PARAM__GROUP = 0xa92ea569d1a9f915f96759ba7cea5f135d011c442b0508dbef76a309e55f4458; @@ -42,7 +43,7 @@ contract GroupGatedFeedRule is IFeedRule, MetadataBased { KeyValue[] calldata, /* primitiveParams */ KeyValue[] calldata /* ruleParams */ ) external view override { - require(IGroup(_groupGate[msg.sender][configSalt]).isMember(postParams.author), "NotAMember()"); + require(IGroup(_groupGate[msg.sender][configSalt]).isMember(postParams.author), Errors.NotAMember()); } function processEditPost( @@ -52,16 +53,16 @@ contract GroupGatedFeedRule is IFeedRule, MetadataBased { KeyValue[] calldata, /* primitiveParams */ KeyValue[] calldata /* ruleParams */ ) external pure override { - revert(); + revert Errors.NotImplemented(); } - function processRemovePost( + function processDeletePost( bytes32, /* configSalt */ uint256, /* postId */ KeyValue[] calldata, /* primitiveParams */ KeyValue[] calldata /* ruleParams */ ) external pure override { - revert(); + revert Errors.NotImplemented(); } function processPostRuleChanges( @@ -70,6 +71,6 @@ contract GroupGatedFeedRule is IFeedRule, MetadataBased { RuleChange[] calldata, /* ruleChanges */ KeyValue[] calldata /* ruleParams */ ) external pure override { - revert(); + revert Errors.NotImplemented(); } } diff --git a/contracts/rules/feed/RestrictedSignersFeedRule.sol b/contracts/rules/feed/RestrictedSignersFeedRule.sol index 08672f0a..8d34faea 100644 --- a/contracts/rules/feed/RestrictedSignersFeedRule.sol +++ b/contracts/rules/feed/RestrictedSignersFeedRule.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {CreatePostParams, EditPostParams} from "contracts/core/interfaces/IFeed.sol"; import {IFeedRule} from "contracts/core/interfaces/IFeedRule.sol"; @@ -49,7 +49,7 @@ contract RestrictedSignersFeedRule is RestrictedSignersRule, IFeedRule { }); } - function processRemovePost( + function processDeletePost( bytes32 configSalt, uint256 postId, KeyValue[] calldata primitiveParams, @@ -57,7 +57,7 @@ contract RestrictedSignersFeedRule is RestrictedSignersRule, IFeedRule { ) external override { _validateRestrictedSignerMessage({ configSalt: configSalt, - functionSelector: IFeedRule.processRemovePost.selector, + functionSelector: IFeedRule.processDeletePost.selector, abiEncodedFunctionParams: abi.encode(postId, EIP712EncodingLib.encodeForEIP712(primitiveParams)), signature: abi.decode(ruleParams[0].value, (EIP712Signature)) }); diff --git a/contracts/rules/feed/SimplePaymentFeedRule.sol b/contracts/rules/feed/SimplePaymentFeedRule.sol index 77e66ed0..43eb925e 100644 --- a/contracts/rules/feed/SimplePaymentFeedRule.sol +++ b/contracts/rules/feed/SimplePaymentFeedRule.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {CreatePostParams, EditPostParams} from "contracts/core/interfaces/IFeed.sol"; import {IFeedRule} from "contracts/core/interfaces/IFeedRule.sol"; @@ -9,6 +9,7 @@ import {AccessControlLib} from "contracts/core/libraries/AccessControlLib.sol"; import {IAccessControl} from "contracts/core/interfaces/IAccessControl.sol"; import {KeyValue, RuleChange} from "contracts/core/types/Types.sol"; import {Events} from "contracts/core/types/Events.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; contract SimplePaymentFeedRule is SimplePaymentRule, IFeedRule { using AccessControlLib for IAccessControl; @@ -60,16 +61,16 @@ contract SimplePaymentFeedRule is SimplePaymentRule, IFeedRule { KeyValue[] calldata, /* primitiveParams */ KeyValue[] calldata /* ruleParams */ ) external pure override { - revert(); + revert Errors.NotImplemented(); } - function processRemovePost( + function processDeletePost( bytes32, /* configSalt */ uint256, /* postId */ KeyValue[] calldata, /* primitiveParams */ KeyValue[] calldata /* ruleParams */ ) external pure override { - revert(); + revert Errors.NotImplemented(); } function processPostRuleChanges( @@ -78,7 +79,7 @@ contract SimplePaymentFeedRule is SimplePaymentRule, IFeedRule { RuleChange[] calldata, /* ruleChanges */ KeyValue[] calldata /* ruleParams */ ) external pure override { - revert(); + revert Errors.NotImplemented(); } function _processPayment( diff --git a/contracts/rules/feed/TokenGatedFeedRule.sol b/contracts/rules/feed/TokenGatedFeedRule.sol index dda22ca7..e67543a8 100644 --- a/contracts/rules/feed/TokenGatedFeedRule.sol +++ b/contracts/rules/feed/TokenGatedFeedRule.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {CreatePostParams, EditPostParams} from "contracts/core/interfaces/IFeed.sol"; import {IFeedRule} from "contracts/core/interfaces/IFeedRule.sol"; @@ -9,13 +9,14 @@ import {IAccessControl} from "contracts/core/interfaces/IAccessControl.sol"; import {AccessControlLib} from "contracts/core/libraries/AccessControlLib.sol"; import {KeyValue, RuleChange} from "contracts/core/types/Types.sol"; import {Events} from "contracts/core/types/Events.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; contract TokenGatedFeedRule is TokenGatedRule, IFeedRule { using AccessControlLib for IAccessControl; using AccessControlLib for address; - /// @custom:keccak lens.permission.SkipTokenGate - uint256 constant PID__SKIP_TOKEN_GATE = uint256(0x42073514d6ebc3c4c46bdc33d53105f5c563a0d184e86952704eb3e7b74ec1ae); + /// @custom:keccak lens.permission.SkipGate + uint256 constant PID__SKIP_GATE = uint256(0xeb7f30e4c97d5211e2534aa42375c26931bd55b57a8101e5eb7918daead714eb); /// @custom:keccak lens.param.accessControl bytes32 constant PARAM__ACCESS_CONTROL = 0xcf3b0fab90208e4185bf857e0f943f6672abffb7d0898e0750beeeb991ae35fa; @@ -28,7 +29,7 @@ contract TokenGatedFeedRule is TokenGatedRule, IFeedRule { mapping(address => mapping(bytes32 => Configuration)) internal _configuration; constructor(string memory metadataURI) TokenGatedRule(metadataURI) { - emit Events.Lens_PermissionId_Available(PID__SKIP_TOKEN_GATE, "lens.permission.SkipTokenGate"); + emit Events.Lens_PermissionId_Available(PID__SKIP_GATE, "lens.permission.SkipGate"); } function configure(bytes32 configSalt, KeyValue[] calldata ruleParams) external override { @@ -59,16 +60,16 @@ contract TokenGatedFeedRule is TokenGatedRule, IFeedRule { KeyValue[] calldata, /* primitiveParams */ KeyValue[] calldata /* ruleParams */ ) external pure override { - revert(); + revert Errors.NotImplemented(); } - function processRemovePost( + function processDeletePost( bytes32, /* configSalt */ uint256, /* postId */ KeyValue[] calldata, /* primitiveParams */ KeyValue[] calldata /* ruleParams */ ) external pure override { - revert(); + revert Errors.NotImplemented(); } function processPostRuleChanges( @@ -77,7 +78,7 @@ contract TokenGatedFeedRule is TokenGatedRule, IFeedRule { RuleChange[] calldata, /* ruleChanges */ KeyValue[] calldata /* ruleParams */ ) external pure override { - revert(); + revert Errors.NotImplemented(); } function _validateTokenBalance( @@ -85,7 +86,7 @@ contract TokenGatedFeedRule is TokenGatedRule, IFeedRule { TokenGateConfiguration memory tokenGateConfiguration, address account ) internal view { - if (!accessControl.hasAccess(account, PID__SKIP_TOKEN_GATE)) { + if (!accessControl.hasAccess(account, PID__SKIP_GATE)) { _validateTokenBalance(tokenGateConfiguration, account); } } diff --git a/contracts/rules/follow/SimplePaymentFollowRule.sol b/contracts/rules/follow/SimplePaymentFollowRule.sol index 7896adfa..cd2a59f9 100644 --- a/contracts/rules/follow/SimplePaymentFollowRule.sol +++ b/contracts/rules/follow/SimplePaymentFollowRule.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IFollowRule} from "contracts/core/interfaces/IFollowRule.sol"; import {SimplePaymentRule} from "contracts/rules/base/SimplePaymentRule.sol"; diff --git a/contracts/rules/follow/TokenGatedFollowRule.sol b/contracts/rules/follow/TokenGatedFollowRule.sol index 94b28acb..da408b29 100644 --- a/contracts/rules/follow/TokenGatedFollowRule.sol +++ b/contracts/rules/follow/TokenGatedFollowRule.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IFollowRule} from "contracts/core/interfaces/IFollowRule.sol"; import {TokenGatedRule} from "contracts/rules/base/TokenGatedRule.sol"; @@ -13,8 +13,8 @@ contract TokenGatedFollowRule is TokenGatedRule, IFollowRule { using AccessControlLib for IAccessControl; using AccessControlLib for address; - /// @custom:keccak lens.permission.SkipTokenGate - uint256 constant PID__SKIP_TOKEN_GATE = uint256(0x42073514d6ebc3c4c46bdc33d53105f5c563a0d184e86952704eb3e7b74ec1ae); + /// @custom:keccak lens.permission.SkipGate + uint256 constant PID__SKIP_GATE = uint256(0xeb7f30e4c97d5211e2534aa42375c26931bd55b57a8101e5eb7918daead714eb); /// @custom:keccak lens.param.accessControl bytes32 constant PARAM__ACCESS_CONTROL = 0xcf3b0fab90208e4185bf857e0f943f6672abffb7d0898e0750beeeb991ae35fa; @@ -27,7 +27,7 @@ contract TokenGatedFollowRule is TokenGatedRule, IFollowRule { mapping(address => mapping(address => mapping(bytes32 => Configuration))) internal _configuration; constructor(string memory metadataURI) TokenGatedRule(metadataURI) { - emit Events.Lens_PermissionId_Available(PID__SKIP_TOKEN_GATE, "lens.permission.SkipTokenGate"); + emit Events.Lens_PermissionId_Available(PID__SKIP_GATE, "lens.permission.SkipGate"); } function configure(bytes32 configSalt, address account, KeyValue[] calldata ruleParams) external override { @@ -57,7 +57,7 @@ contract TokenGatedFollowRule is TokenGatedRule, IFollowRule { TokenGateConfiguration memory tokenGateConfiguration, address account ) internal view { - if (!accessControl.hasAccess(account, PID__SKIP_TOKEN_GATE)) { + if (!accessControl.hasAccess(account, PID__SKIP_GATE)) { _validateTokenBalance(tokenGateConfiguration, account); } } diff --git a/contracts/rules/graph/GroupGatedGraphRule.sol b/contracts/rules/graph/GroupGatedGraphRule.sol index ade51593..9dbd3abf 100644 --- a/contracts/rules/graph/GroupGatedGraphRule.sol +++ b/contracts/rules/graph/GroupGatedGraphRule.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IGraphRule} from "contracts/core/interfaces/IGraphRule.sol"; import {IAccessControl} from "contracts/core/interfaces/IAccessControl.sol"; @@ -9,6 +9,7 @@ import {KeyValue, RuleChange} from "contracts/core/types/Types.sol"; import {Events} from "contracts/core/types/Events.sol"; import {IGroup} from "contracts/core/interfaces/IGroup.sol"; import {MetadataBased} from "contracts/core/base/MetadataBased.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; contract GroupGatedGraphRule is IGraphRule, MetadataBased { using AccessControlLib for IAccessControl; @@ -16,8 +17,8 @@ contract GroupGatedGraphRule is IGraphRule, MetadataBased { event Lens_Rule_MetadataURISet(string metadataURI); - /// @custom:keccak lens.permission.SkipTokenGate - uint256 constant PID__SKIP_TOKEN_GATE = uint256(0x42073514d6ebc3c4c46bdc33d53105f5c563a0d184e86952704eb3e7b74ec1ae); + /// @custom:keccak lens.permission.SkipGate + uint256 constant PID__SKIP_GATE = uint256(0xeb7f30e4c97d5211e2534aa42375c26931bd55b57a8101e5eb7918daead714eb); /// @custom:keccak lens.param.accessControl bytes32 constant PARAM__ACCESS_CONTROL = 0xcf3b0fab90208e4185bf857e0f943f6672abffb7d0898e0750beeeb991ae35fa; @@ -33,7 +34,7 @@ contract GroupGatedGraphRule is IGraphRule, MetadataBased { constructor(string memory metadataURI) { _setMetadataURI(metadataURI); - emit Events.Lens_PermissionId_Available(PID__SKIP_TOKEN_GATE, "lens.permission.SkipTokenGate"); + emit Events.Lens_PermissionId_Available(PID__SKIP_GATE, "lens.permission.SkipGate"); } function _emitMetadataURISet(string memory metadataURI) internal override { @@ -79,7 +80,7 @@ contract GroupGatedGraphRule is IGraphRule, MetadataBased { KeyValue[] calldata, /* primitiveParams */ KeyValue[] calldata /* ruleParams */ ) external pure override { - revert(); + revert Errors.NotImplemented(); } function processFollowRuleChanges( @@ -88,12 +89,12 @@ contract GroupGatedGraphRule is IGraphRule, MetadataBased { RuleChange[] calldata, /* ruleChanges */ KeyValue[] calldata /* ruleParams */ ) external pure override { - revert(); + revert Errors.NotImplemented(); } function _validateGroupMembership(address accessControl, address group, address account) internal view { - if (!accessControl.hasAccess(account, PID__SKIP_TOKEN_GATE)) { - require(IGroup(group).isMember(account), "NotAMember()"); + if (!accessControl.hasAccess(account, PID__SKIP_GATE)) { + require(IGroup(group).isMember(account), Errors.NotAMember()); } } diff --git a/contracts/rules/graph/RestrictedSignersGraphRule.sol b/contracts/rules/graph/RestrictedSignersGraphRule.sol index d3f2c3df..1d159a9d 100644 --- a/contracts/rules/graph/RestrictedSignersGraphRule.sol +++ b/contracts/rules/graph/RestrictedSignersGraphRule.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IGraphRule} from "contracts/core/interfaces/IGraphRule.sol"; import {RestrictedSignersRule, EIP712Signature} from "contracts/rules/base/RestrictedSignersRule.sol"; diff --git a/contracts/rules/graph/TokenGatedGraphRule.sol b/contracts/rules/graph/TokenGatedGraphRule.sol index 16c4cf2d..62db205e 100644 --- a/contracts/rules/graph/TokenGatedGraphRule.sol +++ b/contracts/rules/graph/TokenGatedGraphRule.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IGraphRule} from "contracts/core/interfaces/IGraphRule.sol"; import {TokenGatedRule} from "contracts/rules/base/TokenGatedRule.sol"; @@ -8,13 +8,14 @@ import {IAccessControl} from "contracts/core/interfaces/IAccessControl.sol"; import {AccessControlLib} from "contracts/core/libraries/AccessControlLib.sol"; import {KeyValue, RuleChange} from "contracts/core/types/Types.sol"; import {Events} from "contracts/core/types/Events.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; contract TokenGatedGraphRule is TokenGatedRule, IGraphRule { using AccessControlLib for IAccessControl; using AccessControlLib for address; - /// @custom:keccak lens.permission.SkipTokenGate - uint256 constant PID__SKIP_TOKEN_GATE = uint256(0x42073514d6ebc3c4c46bdc33d53105f5c563a0d184e86952704eb3e7b74ec1ae); + /// @custom:keccak lens.permission.SkipGate + uint256 constant PID__SKIP_GATE = uint256(0xeb7f30e4c97d5211e2534aa42375c26931bd55b57a8101e5eb7918daead714eb); /// @custom:keccak lens.param.accessControl bytes32 constant PARAM__ACCESS_CONTROL = 0xcf3b0fab90208e4185bf857e0f943f6672abffb7d0898e0750beeeb991ae35fa; @@ -27,7 +28,7 @@ contract TokenGatedGraphRule is TokenGatedRule, IGraphRule { mapping(address => mapping(bytes32 => Configuration)) internal _configuration; constructor(string memory metadataURI) TokenGatedRule(metadataURI) { - emit Events.Lens_PermissionId_Available(PID__SKIP_TOKEN_GATE, "lens.permission.SkipTokenGate"); + emit Events.Lens_PermissionId_Available(PID__SKIP_GATE, "lens.permission.SkipGate"); } function configure(bytes32 configSalt, KeyValue[] calldata ruleParams) external { @@ -69,7 +70,7 @@ contract TokenGatedGraphRule is TokenGatedRule, IGraphRule { KeyValue[] calldata, /* primitiveParams */ KeyValue[] calldata /* ruleParams */ ) external pure { - revert(); + revert Errors.NotImplemented(); } function processFollowRuleChanges( @@ -78,7 +79,7 @@ contract TokenGatedGraphRule is TokenGatedRule, IGraphRule { RuleChange[] calldata, /* ruleChanges */ KeyValue[] calldata /* ruleParams */ ) external pure override { - revert(); + revert Errors.NotImplemented(); } function _validateTokenBalance( @@ -86,7 +87,7 @@ contract TokenGatedGraphRule is TokenGatedRule, IGraphRule { TokenGateConfiguration memory tokenGateConfiguration, address account ) internal view { - if (!accessControl.hasAccess(account, PID__SKIP_TOKEN_GATE)) { + if (!accessControl.hasAccess(account, PID__SKIP_GATE)) { _validateTokenBalance(tokenGateConfiguration, account); } } diff --git a/contracts/rules/group/BanMemberGroupRule.sol b/contracts/rules/group/BanMemberGroupRule.sol index 7e23e458..aed065ab 100644 --- a/contracts/rules/group/BanMemberGroupRule.sol +++ b/contracts/rules/group/BanMemberGroupRule.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IGroupRule} from "contracts/core/interfaces/IGroupRule.sol"; import {IAccessControl} from "contracts/core/interfaces/IAccessControl.sol"; @@ -8,6 +8,7 @@ import {AccessControlLib} from "contracts/core/libraries/AccessControlLib.sol"; import {Events} from "contracts/core/types/Events.sol"; import {KeyValue} from "contracts/core/types/Types.sol"; import {MetadataBased} from "contracts/core/base/MetadataBased.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; contract BanMemberGroupRule is IGroupRule, MetadataBased { using AccessControlLib for IAccessControl; @@ -80,7 +81,7 @@ contract BanMemberGroupRule is IGroupRule, MetadataBased { if (_isMemberBanned[msg.sender][configSalt][account]) { for (uint256 i = 0; i < ruleParams.length; i++) { if (ruleParams[i].key == PARAM__BAN_MEMBER) { - require(!abi.decode(ruleParams[i].value, (bool))); // Cannot ban while adding to the group. + require(!abi.decode(ruleParams[i].value, (bool)), Errors.InvalidParameter()); // Cannot ban while adding to the group. _isMemberBanned[msg.sender][configSalt][account] = false; _accessControl[msg.sender][configSalt].requireAccess(originalMsgSender, PID__UNBAN_MEMBER); emit Lens_BanMemberGroupRule_MemberUnbanned(msg.sender, configSalt, account, originalMsgSender); @@ -88,7 +89,7 @@ contract BanMemberGroupRule is IGroupRule, MetadataBased { } } // If member is banned and the param to unban was not passed, revert. - revert(); + revert Errors.Banned(); } } @@ -107,7 +108,7 @@ contract BanMemberGroupRule is IGroupRule, MetadataBased { emit Lens_BanMemberGroupRule_MemberBanned(msg.sender, configSalt, account, originalMsgSender); } else { // Cannot unban while kicking from the group. - require(!_isMemberBanned[msg.sender][configSalt][account]); + require(!_isMemberBanned[msg.sender][configSalt][account], Errors.InvalidParameter()); } return; } @@ -120,7 +121,7 @@ contract BanMemberGroupRule is IGroupRule, MetadataBased { KeyValue[] calldata, /* primitiveParams */ KeyValue[] calldata /* ruleParams */ ) external view override { - require(!_isMemberBanned[msg.sender][configSalt][account]); + require(!_isMemberBanned[msg.sender][configSalt][account], Errors.Banned()); } function processLeaving( @@ -129,6 +130,6 @@ contract BanMemberGroupRule is IGroupRule, MetadataBased { KeyValue[] calldata, /* primitiveParams */ KeyValue[] calldata /* ruleParams */ ) external pure override { - revert(); + revert Errors.NotImplemented(); } } diff --git a/contracts/rules/group/MembershipApprovalGroupRule.sol b/contracts/rules/group/MembershipApprovalGroupRule.sol index 8c32cec6..efb4e4e5 100644 --- a/contracts/rules/group/MembershipApprovalGroupRule.sol +++ b/contracts/rules/group/MembershipApprovalGroupRule.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IGroupRule} from "contracts/core/interfaces/IGroupRule.sol"; import {IAccessControl} from "contracts/core/interfaces/IAccessControl.sol"; @@ -8,6 +8,7 @@ import {AccessControlLib} from "contracts/core/libraries/AccessControlLib.sol"; import {Events} from "contracts/core/types/Events.sol"; import {KeyValue} from "contracts/core/types/Types.sol"; import {MetadataBased} from "contracts/core/base/MetadataBased.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; contract MembershipApprovalGroupRule is IGroupRule, MetadataBased { using AccessControlLib for IAccessControl; @@ -45,19 +46,19 @@ contract MembershipApprovalGroupRule is IGroupRule, MetadataBased { } function requestMembership(bytes32 configSalt, address group) external { - require(!_membershipRequests[group][msg.sender][configSalt].isRequested); + require(!_membershipRequests[group][msg.sender][configSalt].isRequested, Errors.AlreadyExists()); _membershipRequests[group][msg.sender][configSalt].isRequested = true; emit Lens_ApprovalGroupRule_MembershipRequested(group, msg.sender); } function cancelMembershipRequest(bytes32 configSalt, address group) external { - require(_membershipRequests[group][msg.sender][configSalt].isRequested); + require(_membershipRequests[group][msg.sender][configSalt].isRequested, Errors.DoesNotExist()); delete _membershipRequests[group][msg.sender][configSalt]; emit Lens_ApprovalGroupRule_MembershipRequestCancelled(group, msg.sender); } function answerMembershipRequest(bytes32 configSalt, address group, address account, bool isApproved) external { - require(_membershipRequests[group][account][configSalt].isRequested); + require(_membershipRequests[group][account][configSalt].isRequested, Errors.DoesNotExist()); if (isApproved) { _membershipRequests[group][account][configSalt].isApproved = isApproved; emit Lens_ApprovalGroupRule_MembershipApproved(group, account, msg.sender); @@ -65,7 +66,7 @@ contract MembershipApprovalGroupRule is IGroupRule, MetadataBased { delete _membershipRequests[group][account][configSalt]; emit Lens_ApprovalGroupRule_MembershipRejected(group, account, msg.sender); } - require(_accessControl[group][configSalt].hasAccess(msg.sender, PID__APPROVE_MEMBER)); + _accessControl[group][configSalt].requireAccess(msg.sender, PID__APPROVE_MEMBER); } function configure(bytes32 configSalt, KeyValue[] calldata ruleParams) external override { @@ -88,7 +89,7 @@ contract MembershipApprovalGroupRule is IGroupRule, MetadataBased { KeyValue[] calldata /* ruleParams */ ) external override { if (!_membershipRequests[msg.sender][account][configSalt].isApproved) { - require(_accessControl[msg.sender][configSalt].hasAccess(originalMsgSender, PID__APPROVE_MEMBER)); + _accessControl[msg.sender][configSalt].requireAccess(originalMsgSender, PID__APPROVE_MEMBER); emit Lens_ApprovalGroupRule_MembershipApproved(msg.sender, account, originalMsgSender); } delete _membershipRequests[msg.sender][account][configSalt]; @@ -101,7 +102,7 @@ contract MembershipApprovalGroupRule is IGroupRule, MetadataBased { KeyValue[] calldata, /* primitiveParams */ KeyValue[] calldata /* ruleParams */ ) external override { - require(_membershipRequests[msg.sender][account][configSalt].isApproved); + require(_membershipRequests[msg.sender][account][configSalt].isApproved, Errors.NotAllowed()); delete _membershipRequests[msg.sender][account][configSalt]; emit Lens_ApprovalGroupRule_MembershipGranted(msg.sender, account); } @@ -113,7 +114,7 @@ contract MembershipApprovalGroupRule is IGroupRule, MetadataBased { KeyValue[] calldata, /* primitiveParams */ KeyValue[] calldata /* ruleParams */ ) external pure override { - revert(); + revert Errors.NotImplemented(); } function processLeaving( @@ -122,6 +123,6 @@ contract MembershipApprovalGroupRule is IGroupRule, MetadataBased { KeyValue[] calldata, /* primitiveParams */ KeyValue[] calldata /* ruleParams */ ) external pure override { - revert(); + revert Errors.NotImplemented(); } } diff --git a/contracts/rules/group/SimplePaymentGroupRule.sol b/contracts/rules/group/SimplePaymentGroupRule.sol index f3338c46..8548ebd5 100644 --- a/contracts/rules/group/SimplePaymentGroupRule.sol +++ b/contracts/rules/group/SimplePaymentGroupRule.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IGroupRule} from "contracts/core/interfaces/IGroupRule.sol"; import {SimplePaymentRule} from "contracts/rules/base/SimplePaymentRule.sol"; @@ -8,6 +8,7 @@ import {AccessControlLib} from "contracts/core/libraries/AccessControlLib.sol"; import {IAccessControl} from "contracts/core/interfaces/IAccessControl.sol"; import {KeyValue} from "contracts/core/types/Types.sol"; import {Events} from "contracts/core/types/Events.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; contract SimplePaymentGroupRule is SimplePaymentRule, IGroupRule { using AccessControlLib for IAccessControl; @@ -44,7 +45,7 @@ contract SimplePaymentGroupRule is SimplePaymentRule, IGroupRule { KeyValue[] calldata, /* primitiveParams */ KeyValue[] calldata /* ruleParams */ ) external pure { - revert(); + revert Errors.NotImplemented(); } function processRemoval( @@ -54,7 +55,7 @@ contract SimplePaymentGroupRule is SimplePaymentRule, IGroupRule { KeyValue[] calldata, /* primitiveParams */ KeyValue[] calldata /* ruleParams */ ) external pure { - revert(); + revert Errors.NotImplemented(); } function processJoining( @@ -77,7 +78,7 @@ contract SimplePaymentGroupRule is SimplePaymentRule, IGroupRule { KeyValue[] calldata, /* primitiveParams */ KeyValue[] calldata /* ruleParams */ ) external pure { - revert(); + revert Errors.NotImplemented(); } function _processPayment( diff --git a/contracts/rules/group/TokenGatedGroupRule.sol b/contracts/rules/group/TokenGatedGroupRule.sol index 56b49a05..4368ccc1 100644 --- a/contracts/rules/group/TokenGatedGroupRule.sol +++ b/contracts/rules/group/TokenGatedGroupRule.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IGroupRule} from "contracts/core/interfaces/IGroupRule.sol"; import {TokenGatedRule} from "contracts/rules/base/TokenGatedRule.sol"; @@ -8,13 +8,14 @@ import {IAccessControl} from "contracts/core/interfaces/IAccessControl.sol"; import {AccessControlLib} from "contracts/core/libraries/AccessControlLib.sol"; import {KeyValue} from "contracts/core/types/Types.sol"; import {Events} from "contracts/core/types/Events.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; contract TokenGatedGroupRule is TokenGatedRule, IGroupRule { using AccessControlLib for IAccessControl; using AccessControlLib for address; - /// @custom:keccak lens.permission.SkipTokenGate - uint256 constant PID__SKIP_TOKEN_GATE = uint256(0x42073514d6ebc3c4c46bdc33d53105f5c563a0d184e86952704eb3e7b74ec1ae); + /// @custom:keccak lens.permission.SkipGate + uint256 constant PID__SKIP_GATE = uint256(0xeb7f30e4c97d5211e2534aa42375c26931bd55b57a8101e5eb7918daead714eb); /// @custom:keccak lens.param.accessControl bytes32 constant PARAM__ACCESS_CONTROL = 0xcf3b0fab90208e4185bf857e0f943f6672abffb7d0898e0750beeeb991ae35fa; @@ -27,7 +28,7 @@ contract TokenGatedGroupRule is TokenGatedRule, IGroupRule { mapping(address => mapping(bytes32 => Configuration)) internal _configuration; constructor(string memory metadataURI) TokenGatedRule(metadataURI) { - emit Events.Lens_PermissionId_Available(PID__SKIP_TOKEN_GATE, "lens.permission.SkipTokenGate"); + emit Events.Lens_PermissionId_Available(PID__SKIP_GATE, "lens.permission.SkipGate"); } function configure(bytes32 configSalt, KeyValue[] calldata ruleParams) external { @@ -44,7 +45,7 @@ contract TokenGatedGroupRule is TokenGatedRule, IGroupRule { KeyValue[] calldata, /* primitiveParams */ KeyValue[] calldata /* ruleParams */ ) external pure { - revert(); + revert Errors.NotImplemented(); } function processRemoval( @@ -55,7 +56,7 @@ contract TokenGatedGroupRule is TokenGatedRule, IGroupRule { KeyValue[] calldata /* ruleParams */ ) external view { // Anyone can kick out member of the group if they no longer hold the required token balance: - require(!_checkTokenBalance(_configuration[msg.sender][configSalt].tokenGate, account)); + require(!_checkTokenBalance(_configuration[msg.sender][configSalt].tokenGate, account), Errors.NotAllowed()); } function processJoining( @@ -77,7 +78,7 @@ contract TokenGatedGroupRule is TokenGatedRule, IGroupRule { KeyValue[] calldata, /* primitiveParams */ KeyValue[] calldata /* ruleParams */ ) external pure { - revert(); + revert Errors.NotImplemented(); } function _validateTokenBalance( @@ -85,7 +86,7 @@ contract TokenGatedGroupRule is TokenGatedRule, IGroupRule { TokenGateConfiguration memory tokenGateConfiguration, address account ) internal view { - if (!accessControl.hasAccess(account, PID__SKIP_TOKEN_GATE)) { + if (!accessControl.hasAccess(account, PID__SKIP_GATE)) { _validateTokenBalance(tokenGateConfiguration, account); } } diff --git a/contracts/rules/namespace/SimplePaymentNamespaceRule.sol b/contracts/rules/namespace/SimplePaymentNamespaceRule.sol index e1768ae7..10dc369b 100644 --- a/contracts/rules/namespace/SimplePaymentNamespaceRule.sol +++ b/contracts/rules/namespace/SimplePaymentNamespaceRule.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IAccessControl} from "contracts/core/interfaces/IAccessControl.sol"; import {INamespaceRule} from "contracts/core/interfaces/INamespaceRule.sol"; diff --git a/contracts/rules/namespace/TokenGatedNamespaceRule.sol b/contracts/rules/namespace/TokenGatedNamespaceRule.sol index b4c457b7..a5e0aa10 100644 --- a/contracts/rules/namespace/TokenGatedNamespaceRule.sol +++ b/contracts/rules/namespace/TokenGatedNamespaceRule.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IAccessControl} from "contracts/core/interfaces/IAccessControl.sol"; import {INamespaceRule} from "contracts/core/interfaces/INamespaceRule.sol"; @@ -13,8 +13,8 @@ contract TokenGatedNamespaceRule is TokenGatedRule, INamespaceRule { using AccessControlLib for IAccessControl; using AccessControlLib for address; - /// @custom:keccak lens.permission.SkipTokenGate - uint256 constant PID__SKIP_TOKEN_GATE = uint256(0x42073514d6ebc3c4c46bdc33d53105f5c563a0d184e86952704eb3e7b74ec1ae); + /// @custom:keccak lens.permission.SkipGate + uint256 constant PID__SKIP_GATE = uint256(0xeb7f30e4c97d5211e2534aa42375c26931bd55b57a8101e5eb7918daead714eb); /// @custom:keccak lens.param.accessControl bytes32 constant PARAM__ACCESS_CONTROL = 0xcf3b0fab90208e4185bf857e0f943f6672abffb7d0898e0750beeeb991ae35fa; @@ -27,7 +27,7 @@ contract TokenGatedNamespaceRule is TokenGatedRule, INamespaceRule { mapping(address => mapping(bytes32 => Configuration)) internal _configuration; constructor(string memory metadataURI) TokenGatedRule(metadataURI) { - emit Events.Lens_PermissionId_Available(PID__SKIP_TOKEN_GATE, "lens.permission.SkipTokenGate"); + emit Events.Lens_PermissionId_Available(PID__SKIP_GATE, "lens.permission.SkipGate"); } function configure(bytes32 configSalt, KeyValue[] calldata ruleParams) external { @@ -101,7 +101,7 @@ contract TokenGatedNamespaceRule is TokenGatedRule, INamespaceRule { TokenGateConfiguration memory tokenGateConfiguration, address account ) internal view { - if (!accessControl.hasAccess(account, PID__SKIP_TOKEN_GATE)) { + if (!accessControl.hasAccess(account, PID__SKIP_GATE)) { _validateTokenBalance(tokenGateConfiguration, account); } } diff --git a/contracts/rules/namespace/UsernameCharsetNamespaceRule.sol b/contracts/rules/namespace/UsernameCharsetNamespaceRule.sol index 6e77f932..ac4a0e1c 100644 --- a/contracts/rules/namespace/UsernameCharsetNamespaceRule.sol +++ b/contracts/rules/namespace/UsernameCharsetNamespaceRule.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IAccessControl} from "contracts/core/interfaces/IAccessControl.sol"; import {INamespaceRule} from "contracts/core/interfaces/INamespaceRule.sol"; @@ -8,6 +8,7 @@ import {AccessControlLib} from "contracts/core/libraries/AccessControlLib.sol"; import {Events} from "contracts/core/types/Events.sol"; import {KeyValue} from "contracts/core/types/Types.sol"; import {MetadataBased} from "contracts/core/base/MetadataBased.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; contract UsernameCharsetNamespaceRule is INamespaceRule, MetadataBased { event Lens_Rule_MetadataURISet(string metadataURI); @@ -91,7 +92,7 @@ contract UsernameCharsetNamespaceRule is INamespaceRule, MetadataBased { KeyValue[] calldata, /* primitiveParams */ KeyValue[] calldata /* ruleParams */ ) external pure override { - revert(); + revert Errors.NotImplemented(); } function processAssigning( @@ -102,7 +103,7 @@ contract UsernameCharsetNamespaceRule is INamespaceRule, MetadataBased { KeyValue[] calldata, /* primitiveParams */ KeyValue[] calldata /* ruleParams */ ) external pure override { - revert(); + revert Errors.NotImplemented(); } function processUnassigning( @@ -113,7 +114,7 @@ contract UsernameCharsetNamespaceRule is INamespaceRule, MetadataBased { KeyValue[] calldata, /* primitiveParams */ KeyValue[] calldata /* ruleParams */ ) external pure override { - revert(); + revert Errors.NotImplemented(); } function _processRestrictions(string calldata username, CharsetRestrictions memory charsetRestrictions) @@ -121,39 +122,24 @@ contract UsernameCharsetNamespaceRule is INamespaceRule, MetadataBased { pure { // Cannot start with a character in the cannotStartWith charset - require( - !_isInCharset(bytes(username)[0], charsetRestrictions.cannotStartWith), - "UsernameCharsetRule: Username cannot start with specified character" - ); + require(!_isInCharset(bytes(username)[0], charsetRestrictions.cannotStartWith), Errors.CannotStartWithThat()); // Check if the username contains only allowed characters for (uint256 i = 0; i < bytes(username).length; i++) { bytes1 char = bytes(username)[i]; // Check disallowed chars first - require( - !_isInCharset(char, charsetRestrictions.customDisallowedCharset), - "UsernameCharsetRule: Username contains disallowed character" - ); + require(!_isInCharset(char, charsetRestrictions.customDisallowedCharset), Errors.NotAllowed()); // Check allowed charsets next if (_isNumeric(char)) { - require(charsetRestrictions.allowNumeric, "UsernameCharsetRule: Username cannot contain numbers"); + require(charsetRestrictions.allowNumeric, Errors.NotAllowed()); } else if (_isLatinLowercase(char)) { - require( - charsetRestrictions.allowLatinLowercase, - "UsernameCharsetRule: Username cannot contain lowercase latin characters" - ); + require(charsetRestrictions.allowLatinLowercase, Errors.NotAllowed()); } else if (_isLatinUppercase(char)) { - require( - charsetRestrictions.allowLatinUppercase, - "UsernameCharsetRule: Username cannot contain uppercase latin characters" - ); + require(charsetRestrictions.allowLatinUppercase, Errors.NotAllowed()); } else if (bytes(charsetRestrictions.customAllowedCharset).length > 0) { - require( - _isInCharset(char, charsetRestrictions.customAllowedCharset), - "UsernameCharsetRule: Username contains disallowed character" - ); + require(_isInCharset(char, charsetRestrictions.customAllowedCharset), Errors.NotAllowed()); } else { // If not in any of the above charsets, reject - revert("UsernameCharsetRule: Username contains disallowed character"); + revert Errors.NotAllowed(); } } } diff --git a/contracts/rules/namespace/UsernameLengthNamespaceRule.sol b/contracts/rules/namespace/UsernameLengthNamespaceRule.sol index 8436f883..bee137ba 100644 --- a/contracts/rules/namespace/UsernameLengthNamespaceRule.sol +++ b/contracts/rules/namespace/UsernameLengthNamespaceRule.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IAccessControl} from "contracts/core/interfaces/IAccessControl.sol"; import {INamespaceRule} from "contracts/core/interfaces/INamespaceRule.sol"; @@ -8,6 +8,7 @@ import {AccessControlLib} from "contracts/core/libraries/AccessControlLib.sol"; import {Events} from "contracts/core/types/Events.sol"; import {KeyValue} from "contracts/core/types/Types.sol"; import {MetadataBased} from "contracts/core/base/MetadataBased.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; contract UsernameLengthNamespaceRule is INamespaceRule, MetadataBased { event Lens_Rule_MetadataURISet(string metadataURI); @@ -60,7 +61,8 @@ contract UsernameLengthNamespaceRule is INamespaceRule, MetadataBased { configuration.accessControl.verifyHasAccessFunction(); require( configuration.lengthRestrictions.max == 0 - || configuration.lengthRestrictions.min <= configuration.lengthRestrictions.max + || configuration.lengthRestrictions.min <= configuration.lengthRestrictions.max, + Errors.InvalidParameter() ); // Min length cannot be greater than max length _configuration[msg.sender][configSalt] = configuration; } @@ -79,13 +81,13 @@ contract UsernameLengthNamespaceRule is INamespaceRule, MetadataBased { configuration.lengthRestrictions.min != 0 && !configuration.accessControl.hasAccess(originalMsgSender, PID__SKIP_MIN_LENGTH_RESTRICTION) ) { - require(usernameLength >= configuration.lengthRestrictions.min, "Username: too short"); + require(usernameLength >= configuration.lengthRestrictions.min, Errors.InvalidParameter()); } if ( configuration.lengthRestrictions.max != 0 && !configuration.accessControl.hasAccess(originalMsgSender, PID__SKIP_MAX_LENGTH_RESTRICTION) ) { - require(usernameLength <= configuration.lengthRestrictions.max, "Username: too long"); + require(usernameLength <= configuration.lengthRestrictions.max, Errors.InvalidParameter()); } } @@ -96,7 +98,7 @@ contract UsernameLengthNamespaceRule is INamespaceRule, MetadataBased { KeyValue[] calldata, /* primitiveParams */ KeyValue[] calldata /* ruleParams */ ) external pure override { - revert(); + revert Errors.NotImplemented(); } function processAssigning( @@ -107,7 +109,7 @@ contract UsernameLengthNamespaceRule is INamespaceRule, MetadataBased { KeyValue[] calldata, /* primitiveParams */ KeyValue[] calldata /* ruleParams */ ) external pure override { - revert(); + revert Errors.NotImplemented(); } function processUnassigning( @@ -118,7 +120,7 @@ contract UsernameLengthNamespaceRule is INamespaceRule, MetadataBased { KeyValue[] calldata, /* primitiveParams */ KeyValue[] calldata /* ruleParams */ ) external pure override { - revert(); + revert Errors.NotImplemented(); } function _extractConfigurationFromParams(KeyValue[] calldata params) internal pure returns (Configuration memory) { diff --git a/contracts/rules/namespace/UsernameReservedNamespaceRule.sol b/contracts/rules/namespace/UsernameReservedNamespaceRule.sol index 7b965652..0621b0c9 100644 --- a/contracts/rules/namespace/UsernameReservedNamespaceRule.sol +++ b/contracts/rules/namespace/UsernameReservedNamespaceRule.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IAccessControl} from "contracts/core/interfaces/IAccessControl.sol"; import {INamespaceRule} from "contracts/core/interfaces/INamespaceRule.sol"; @@ -8,6 +8,7 @@ import {AccessControlLib} from "contracts/core/libraries/AccessControlLib.sol"; import {Events} from "contracts/core/types/Events.sol"; import {KeyValue} from "contracts/core/types/Types.sol"; import {MetadataBased} from "contracts/core/base/MetadataBased.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; contract UsernameReservedNamespaceRule is INamespaceRule, MetadataBased { event Lens_Rule_MetadataURISet(string metadataURI); @@ -61,7 +62,10 @@ contract UsernameReservedNamespaceRule is INamespaceRule, MetadataBased { } else if (ruleParams[i].key == PARAM__USERNAMES_TO_RESERVE) { string[] memory usernamesToReserve = abi.decode(ruleParams[i].value, (string[])); for (uint256 j = 0; j < usernamesToReserve.length; j++) { - require(!_isUsernameReserved[msg.sender][configSalt][usernamesToReserve[j]]); + require( + !_isUsernameReserved[msg.sender][configSalt][usernamesToReserve[j]], + Errors.RedundantStateChange() + ); _isUsernameReserved[msg.sender][configSalt][usernamesToReserve[j]] = true; emit Lens_UsernameReservedNamespaceRule_UsernameReserved( msg.sender, configSalt, usernamesToReserve[j], usernamesToReserve[j] @@ -70,7 +74,9 @@ contract UsernameReservedNamespaceRule is INamespaceRule, MetadataBased { } else if (ruleParams[i].key == PARAM__USERNAMES_TO_RELEASE) { string[] memory usernamesToRelease = abi.decode(ruleParams[i].value, (string[])); for (uint256 j = 0; j < usernamesToRelease.length; j++) { - require(_isUsernameReserved[msg.sender][configSalt][usernamesToRelease[j]]); + require( + _isUsernameReserved[msg.sender][configSalt][usernamesToRelease[j]], Errors.RedundantStateChange() + ); _isUsernameReserved[msg.sender][configSalt][usernamesToRelease[j]] = false; emit Lens_UsernameReservedNamespaceRule_UsernameReleased( msg.sender, configSalt, usernamesToRelease[j], usernamesToRelease[j] @@ -105,7 +111,7 @@ contract UsernameReservedNamespaceRule is INamespaceRule, MetadataBased { KeyValue[] calldata, /* primitiveParams */ KeyValue[] calldata /* ruleParams */ ) external pure override { - revert(); + revert Errors.NotImplemented(); } function processAssigning( @@ -116,7 +122,7 @@ contract UsernameReservedNamespaceRule is INamespaceRule, MetadataBased { KeyValue[] calldata, /* primitiveParams */ KeyValue[] calldata /* ruleParams */ ) external pure override { - revert(); + revert Errors.NotImplemented(); } function processUnassigning( @@ -127,6 +133,6 @@ contract UsernameReservedNamespaceRule is INamespaceRule, MetadataBased { KeyValue[] calldata, /* primitiveParams */ KeyValue[] calldata /* ruleParams */ ) external pure override { - revert(); + revert Errors.NotImplemented(); } } diff --git a/contracts/rules/post/FollowersOnlyPostRule.sol b/contracts/rules/post/FollowersOnlyPostRule.sol index 24e25ddc..8d9baa40 100644 --- a/contracts/rules/post/FollowersOnlyPostRule.sol +++ b/contracts/rules/post/FollowersOnlyPostRule.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.0; +pragma solidity ^0.8.26; import {IPostRule} from "contracts/core/interfaces/IPostRule.sol"; import {IGraph} from "contracts/core/interfaces/IGraph.sol"; @@ -8,6 +8,7 @@ import {IFeed} from "contracts/core/interfaces/IFeed.sol"; import {KeyValue} from "contracts/core/types/Types.sol"; import {CreatePostParams, EditPostParams} from "contracts/core/interfaces/IFeed.sol"; import {MetadataBased} from "contracts/core/base/MetadataBased.sol"; +import {Errors} from "contracts/core/types/Errors.sol"; contract FollowersOnlyPostRule is IPostRule, MetadataBased { event Lens_Rule_MetadataURISet(string metadataURI); @@ -52,7 +53,10 @@ contract FollowersOnlyPostRule is IPostRule, MetadataBased { } } IGraph(configuration.graph).isFollowing(address(this), msg.sender); // Verifies the provided address is a graph - require(configuration.repliesRestricted || configuration.repostsRestricted || configuration.quotesRestricted); + require( + configuration.repliesRestricted || configuration.repostsRestricted || configuration.quotesRestricted, + Errors.InvalidParameter() + ); _configuration[msg.sender][configSalt][postId] = configuration; } @@ -70,7 +74,9 @@ contract FollowersOnlyPostRule is IPostRule, MetadataBased { IGraph graph = IGraph(configuration.graph); address rootPostAuthor = feed.getPostAuthor(rootPostId); address newPostAuthor = feed.getPostAuthor(postId); - require(graph.isFollowing({followerAccount: newPostAuthor, targetAccount: rootPostAuthor})); + require( + graph.isFollowing({followerAccount: newPostAuthor, targetAccount: rootPostAuthor}), Errors.NotFollowing() + ); } } @@ -82,7 +88,7 @@ contract FollowersOnlyPostRule is IPostRule, MetadataBased { KeyValue[] calldata, /* primitiveParams */ KeyValue[] calldata /* ruleParams */ ) external pure override { - revert(); + revert Errors.NotImplemented(); } // TODO: This function smells weird, we should reconsider going back to the processQuote/Reply/Repost selectors... diff --git a/coverage.sh b/coverage.sh new file mode 100644 index 00000000..59b9d81a --- /dev/null +++ b/coverage.sh @@ -0,0 +1,4 @@ +rm -fr coverage lcov.info +mkdir -p coverage +forge coverage --report lcov +genhtml --ignore-errors inconsistent --ignore-errors corrupt --ignore-errors category --rc derive_function_end_line=0 lcov.info -o coverage/html --branch-coverage >/dev/null 2>&1 || { echo "Error generating coverage report"; exit 1; } diff --git a/deploy/deployActions.ts b/deploy/deployActions.ts index 7cc35703..34b296c9 100644 --- a/deploy/deployActions.ts +++ b/deploy/deployActions.ts @@ -9,6 +9,7 @@ export async function deployActions(actionHub: string): Promise { const contracts: ContractInfo[] = [ // Actions { contractName: 'TippingAccountAction', contractType: ContractType.Action, constructorArguments: [actionHub, metadataURI] }, + { contractName: 'TippingPostAction', contractType: ContractType.Action, constructorArguments: [actionHub, metadataURI] }, { contractName: 'SimpleCollectAction', contractType: ContractType.Action, constructorArguments: [actionHub, metadataURI] }, ]; diff --git a/foundry.toml b/foundry.toml index 9a9e0308..9c6d9180 100644 --- a/foundry.toml +++ b/foundry.toml @@ -4,12 +4,19 @@ out = 'out' libs = ['node_modules', 'lib'] test = 'test' cache_path = 'cache_forge' -solc_version = '0.8.17' +solc_version = '0.8.28' +# via_ir = true +optimizer = false +# optimizer_runs = 10 -# [profile.default.zksync] -# # Compile contracts for zkVM +[profile.default.fuzz] +runs = 1024 +no_zksync_reserved_addresses = true + +[profile.default.zksync] +# Compile contracts for zkVM # compile = true -# # Enable zkVM at startup, needs `compile = true` to have effect +# Enable zkVM at startup, needs `compile = true` to have effect # startup = true # # By default the latest version is used # zksolc = "1.5.0" @@ -24,9 +31,9 @@ solc_version = '0.8.17' # # Force compilation via EVMLA instead of Yul codegen pipeline # force_evmla = false # # Enable optimizer on zksolc (defaults to true) -# optimizer = true -# # zksolc optimizer mode (0 | 1 | 2 | 3 | s | z) -# optimizer_mode = '3' +optimizer = true +# zksolc optimizer mode (0 | 1 | 2 | 3 | s | z) +optimizer_mode = '1' # # zksolc optimizer details # optimizer_details = { ... } diff --git a/hardhat.config.ts b/hardhat.config.ts index 282ea9df..c0d301b7 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -40,7 +40,7 @@ const config: HardhatUserConfig = { }, }, solidity: { - version: '0.8.17', + version: '0.8.28', }, }; diff --git a/package.json b/package.json index 48809f33..bb3a1615 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,8 @@ "compile": "hardhat compile", "clean": "hardhat clean", "test": "hardhat test --network hardhat", + "coverage": "forge coverage", + "coverage:report": "bash coverage.sh", "prepare": "husky", "format": "forge fmt", "prod:abis": "npm run abis && cp -r out/abis ./ABIs", diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index c31608c4..e2a9be29 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -35,6 +35,10 @@ import {AccountBlockingRule} from "contracts/rules/base/AccountBlockingRule.sol" import {GroupGatedFeedRule} from "contracts/rules/feed/GroupGatedFeedRule.sol"; contract MyScript is Script { + function testMyScript() public { + // Prevents being counted in Foundry Coverage + } + IAccessControl simpleAccessControl; ITokenURIProvider simpleTokenURIProvider; address proxyAdminLock; diff --git a/test/Account.t.sol b/test/Account.t.sol index bf8aceaa..fcec540c 100644 --- a/test/Account.t.sol +++ b/test/Account.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity 0.8.17; +pragma solidity ^0.8.26; import "forge-std/Test.sol"; import "./helpers/TypeHelpers.sol"; @@ -8,9 +8,8 @@ import {IAccount, AccountManagerPermissions} from "@extensions/account/IAccount. import {Account} from "@extensions/account/Account.sol"; import {Feed} from "@core/primitives/Feed/Feed.sol"; import {IFeed, Post, CreatePostParams} from "@core/interfaces/IFeed.sol"; -import {OwnerAdminOnlyAccessControl} from "@extensions/access/OwnerAdminOnlyAccessControl.sol"; -import {IAccessControl} from "@core/interfaces/IAccessControl.sol"; import {BaseDeployments} from "test/helpers/BaseDeployments.sol"; +import {Errors} from "@core/types/Errors.sol"; contract AccountTest is Test, BaseDeployments { address owner = makeAddr("OWNER"); @@ -135,19 +134,19 @@ contract AccountTest is Test, BaseDeployments { function testCannotAddAccountManager_Twice() public { vm.prank(owner); - vm.expectRevert("Account manager already exists"); + vm.expectRevert(Errors.RedundantStateChange.selector); account.addAccountManager(manager, AccountManagerPermissions(true, true, true, true)); } function testCannotAdd_Owner_AsAccountManager() public { vm.prank(owner); - vm.expectRevert("Cannot add owner as account manager"); + vm.expectRevert(Errors.InvalidParameter.selector); account.addAccountManager(owner, AccountManagerPermissions(true, true, true, true)); } function testCannotAdd_ZeroAddress_AsManagerTwiceOrWrongly() public { vm.prank(owner); - vm.expectRevert("Cannot add zero address as account manager"); + vm.expectRevert(Errors.InvalidParameter.selector); account.addAccountManager(address(0), AccountManagerPermissions(true, true, true, true)); } diff --git a/test/access-control/OwnerAdminOnlyAccessControl.t.sol b/test/access-control/OwnerAdminOnlyAccessControl.t.sol index 6a7f0ee2..ad8af061 100644 --- a/test/access-control/OwnerAdminOnlyAccessControl.t.sol +++ b/test/access-control/OwnerAdminOnlyAccessControl.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity 0.8.17; +pragma solidity ^0.8.26; import "forge-std/Test.sol"; import {OwnerAdminOnlyAccessControl} from "contracts/extensions/access/OwnerAdminOnlyAccessControl.sol"; diff --git a/test/app/App.t.sol b/test/app/App.t.sol index 8efaba93..a4629e2d 100644 --- a/test/app/App.t.sol +++ b/test/app/App.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity 0.8.17; +pragma solidity ^0.8.26; import "forge-std/Test.sol"; import "../helpers/TypeHelpers.sol"; diff --git a/test/factories/LensFactory.t.sol b/test/factories/LensFactory.t.sol index ab845043..dd3cebbc 100644 --- a/test/factories/LensFactory.t.sol +++ b/test/factories/LensFactory.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity 0.8.17; +pragma solidity ^0.8.26; import "forge-std/Test.sol"; -import {LensFactory} from "@extensions/factories/LensFactory.sol"; +import {LensFactory, CreateAccountParams, CreateUsernameParams} from "@extensions/factories/LensFactory.sol"; import {AccountFactory} from "@extensions/factories/AccountFactory.sol"; import {AppFactory} from "@extensions/factories/AppFactory.sol"; import {GroupFactory} from "@extensions/factories/GroupFactory.sol"; @@ -50,22 +50,28 @@ contract LensFactoryTest is Test, BaseDeployments { } function testCreateAccountWithUsernameFree() public { - lensFactory.createAccountWithUsernameFree({ + CreateAccountParams memory accountParams = CreateAccountParams({ metadataURI: "someMetadataURI", owner: address(this), accountManagers: _emptyAddressArray(), accountManagersPermissions: new AccountManagerPermissions[](0), - namespacePrimitiveAddress: address(namespace), - username: "myTestUsername", accountCreationSourceStamp: _emptySourceStamp(), + accountExtraData: _emptyKeyValueArray() + }); + CreateUsernameParams memory usernameParams = CreateUsernameParams({ + username: "myTestUsername", createUsernameCustomParams: _emptyKeyValueArray(), createUsernameRuleProcessingParams: _emptyRuleProcessingParamsArray(), assignUsernameCustomParams: _emptyKeyValueArray(), unassignAccountRuleProcessingParams: _emptyRuleProcessingParamsArray(), assignRuleProcessingParams: _emptyRuleProcessingParamsArray(), - accountExtraData: _emptyKeyValueArray(), usernameExtraData: _emptyKeyValueArray() }); + lensFactory.createAccountWithUsernameFree({ + accountParams: accountParams, + namespacePrimitiveAddress: address(namespace), + usernameParams: usernameParams + }); } function testGraphFollowWithFactorySetup() public { diff --git a/test/helpers/BaseDeployments.sol b/test/helpers/BaseDeployments.sol index 03221485..88d97aab 100644 --- a/test/helpers/BaseDeployments.sol +++ b/test/helpers/BaseDeployments.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity 0.8.17; +pragma solidity ^0.8.26; import "forge-std/Test.sol"; diff --git a/test/helpers/TypeHelpers.sol b/test/helpers/TypeHelpers.sol index f9d9d2b1..0b8f4664 100644 --- a/test/helpers/TypeHelpers.sol +++ b/test/helpers/TypeHelpers.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity ^0.8.13; +pragma solidity ^0.8.26; import {RuleChange, RuleProcessingParams, SourceStamp, KeyValue} from "contracts/core/types/Types.sol"; diff --git a/test/primitives/Feed.t.sol b/test/primitives/Feed.t.sol index f92036d8..caaaeeb8 100644 --- a/test/primitives/Feed.t.sol +++ b/test/primitives/Feed.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity 0.8.17; +pragma solidity ^0.8.26; import "forge-std/Test.sol"; import {IAccessControl} from "@core/interfaces/IAccessControl.sol"; diff --git a/test/primitives/Graph.t.sol b/test/primitives/Graph.t.sol index f789367c..08b88dbd 100644 --- a/test/primitives/Graph.t.sol +++ b/test/primitives/Graph.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity 0.8.17; +pragma solidity ^0.8.26; import "forge-std/Test.sol"; import {IAccessControl} from "@core/interfaces/IAccessControl.sol"; diff --git a/test/primitives/Group.t.sol b/test/primitives/Group.t.sol index 3fc110f9..98c30efe 100644 --- a/test/primitives/Group.t.sol +++ b/test/primitives/Group.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity 0.8.17; +pragma solidity ^0.8.26; import "forge-std/Test.sol"; import {IAccessControl} from "@core/interfaces/IAccessControl.sol"; diff --git a/test/primitives/Namespace.t.sol b/test/primitives/Namespace.t.sol index 4c4768d6..9e097c7e 100644 --- a/test/primitives/Namespace.t.sol +++ b/test/primitives/Namespace.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: UNLICENSED // Copyright (C) 2024 Lens Labs. All Rights Reserved. -pragma solidity 0.8.17; +pragma solidity ^0.8.26; import "forge-std/Test.sol"; import {IAccessControl} from "@core/interfaces/IAccessControl.sol";