mirror of
https://github.com/lens-protocol/core.git
synced 2026-04-22 03:02:03 -04:00
feat: ERC2981CollectionRoyalties added
This commit is contained in:
@@ -2,13 +2,14 @@
|
||||
|
||||
pragma solidity 0.8.15;
|
||||
|
||||
import {ERC2981CollectionRoyalties} from './base/ERC2981CollectionRoyalties.sol';
|
||||
import {ERC721Enumerable} from './base/ERC721Enumerable.sol';
|
||||
import {Errors} from '../libraries/Errors.sol';
|
||||
import {Events} from '../libraries/Events.sol';
|
||||
import {ICollectNFT} from '../interfaces/ICollectNFT.sol';
|
||||
import {IERC721} from '@openzeppelin/contracts/token/ERC721/IERC721.sol';
|
||||
import {ILensHub} from '../interfaces/ILensHub.sol';
|
||||
import {Errors} from '../libraries/Errors.sol';
|
||||
import {Events} from '../libraries/Events.sol';
|
||||
import {LensNFTBase} from './base/LensNFTBase.sol';
|
||||
import {ERC721Enumerable} from './base/ERC721Enumerable.sol';
|
||||
|
||||
/**
|
||||
* @title CollectNFT
|
||||
@@ -17,8 +18,9 @@ import {ERC721Enumerable} from './base/ERC721Enumerable.sol';
|
||||
* @notice This is the NFT contract that is minted upon collecting a given publication. It is cloned upon
|
||||
* the first collect for a given publication, and the token URI points to the original publication's contentURI.
|
||||
*/
|
||||
contract CollectNFT is LensNFTBase, ICollectNFT {
|
||||
contract CollectNFT is LensNFTBase, ERC2981CollectionRoyalties, ICollectNFT {
|
||||
address public immutable HUB;
|
||||
uint8 internal constant ROYALTIES_IN_BASIS_POINTS_SLOT = 15;
|
||||
|
||||
uint256 internal _profileId;
|
||||
uint256 internal _pubId;
|
||||
@@ -26,11 +28,7 @@ contract CollectNFT is LensNFTBase, ICollectNFT {
|
||||
|
||||
bool private _initialized;
|
||||
|
||||
uint256 internal _royaltyBasisPoints;
|
||||
|
||||
// bytes4(keccak256('royaltyInfo(uint256,uint256)')) == 0x2a55205a
|
||||
bytes4 internal constant INTERFACE_ID_ERC2981 = 0x2a55205a;
|
||||
uint16 internal constant BASIS_POINTS = 10000;
|
||||
uint256 internal _royaltiesInBasisPoints;
|
||||
|
||||
// We create the CollectNFT with the pre-computed HUB address before deploying the hub proxy in order
|
||||
// to initialize the hub proxy at construction.
|
||||
@@ -49,7 +47,7 @@ contract CollectNFT is LensNFTBase, ICollectNFT {
|
||||
) external override {
|
||||
if (_initialized) revert Errors.Initialized();
|
||||
_initialized = true;
|
||||
_royaltyBasisPoints = 1000; // 10% of royalties
|
||||
_setRoyalty(1000); // 10% of royalties
|
||||
_profileId = profileId;
|
||||
_pubId = pubId;
|
||||
super._initialize(name, symbol);
|
||||
@@ -76,42 +74,6 @@ contract CollectNFT is LensNFTBase, ICollectNFT {
|
||||
return ILensHub(HUB).getContentURI(_profileId, _pubId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Changes the royalty percentage for secondary sales. Can only be called publication's
|
||||
* profile owner.
|
||||
*
|
||||
* @param royaltyBasisPoints The royalty percentage meassured in basis points. Each basis point
|
||||
* represents 0.01%.
|
||||
*/
|
||||
function setRoyalty(uint256 royaltyBasisPoints) external {
|
||||
if (IERC721(HUB).ownerOf(_profileId) == msg.sender) {
|
||||
if (royaltyBasisPoints > BASIS_POINTS) {
|
||||
revert Errors.InvalidParameter();
|
||||
} else {
|
||||
_royaltyBasisPoints = royaltyBasisPoints;
|
||||
}
|
||||
} else {
|
||||
revert Errors.NotProfileOwner();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Called with the sale price to determine how much royalty
|
||||
* is owed and to whom.
|
||||
*
|
||||
* @param tokenId The token ID of the NFT queried for royalty information.
|
||||
* @param salePrice The sale price of the NFT specified.
|
||||
* @return A tuple with the address who should receive the royalties and the royalty
|
||||
* payment amount for the given sale price.
|
||||
*/
|
||||
function royaltyInfo(uint256 tokenId, uint256 salePrice)
|
||||
external
|
||||
view
|
||||
returns (address, uint256)
|
||||
{
|
||||
return (IERC721(HUB).ownerOf(_profileId), (salePrice * _royaltyBasisPoints) / BASIS_POINTS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev See {IERC165-supportsInterface}.
|
||||
*/
|
||||
@@ -119,10 +81,26 @@ contract CollectNFT is LensNFTBase, ICollectNFT {
|
||||
public
|
||||
view
|
||||
virtual
|
||||
override(ERC721Enumerable)
|
||||
override(ERC2981CollectionRoyalties, ERC721Enumerable)
|
||||
returns (bool)
|
||||
{
|
||||
return interfaceId == INTERFACE_ID_ERC2981 || super.supportsInterface(interfaceId);
|
||||
return
|
||||
ERC2981CollectionRoyalties.supportsInterface(interfaceId) ||
|
||||
ERC721Enumerable.supportsInterface(interfaceId);
|
||||
}
|
||||
|
||||
function _getReceiver(uint256 tokenId) internal view override returns (address) {
|
||||
return IERC721(HUB).ownerOf(_profileId);
|
||||
}
|
||||
|
||||
function _beforeRoyaltiesSet(uint256 royaltiesInBasisPoints) internal override {
|
||||
if (IERC721(HUB).ownerOf(_profileId) != msg.sender) {
|
||||
revert Errors.NotProfileOwner();
|
||||
}
|
||||
}
|
||||
|
||||
function _getRoyaltiesInBasisPointsSlot() internal view override returns (uint256) {
|
||||
return ROYALTIES_IN_BASIS_POINTS_SLOT;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,8 @@ pragma solidity 0.8.15;
|
||||
|
||||
import '../libraries/Constants.sol';
|
||||
import {DataTypes} from '../libraries/DataTypes.sol';
|
||||
import {ERC2981CollectionRoyalties} from './base/ERC2981CollectionRoyalties.sol';
|
||||
import {ERC721Enumerable} from './base/ERC721Enumerable.sol';
|
||||
import {ERC721Time} from './base/ERC721Time.sol';
|
||||
import {Errors} from '../libraries/Errors.sol';
|
||||
import {Events} from '../libraries/Events.sol';
|
||||
@@ -37,14 +39,14 @@ struct FollowData {
|
||||
uint256 recoverableBy;
|
||||
}
|
||||
|
||||
contract FollowNFT is HubRestricted, LensNFTBase, IFollowNFT {
|
||||
contract FollowNFT is HubRestricted, LensNFTBase, ERC2981CollectionRoyalties, IFollowNFT {
|
||||
using Strings for uint256;
|
||||
|
||||
bytes32 internal constant DELEGATE_BY_SIG_TYPEHASH =
|
||||
keccak256(
|
||||
'DelegateBySig(address delegator,address delegatee,uint256 nonce,uint256 deadline)'
|
||||
);
|
||||
uint16 internal constant BASIS_POINTS = 10000;
|
||||
uint8 internal constant ROYALTIES_IN_BASIS_POINTS_SLOT = 23;
|
||||
|
||||
mapping(address => mapping(uint256 => Snapshot)) internal _snapshots;
|
||||
// TODO: Check that nobody has used this feature before doing this mapping modifiation, otherwise use new slot.
|
||||
@@ -57,12 +59,12 @@ contract FollowNFT is HubRestricted, LensNFTBase, IFollowNFT {
|
||||
uint128 internal _followers;
|
||||
|
||||
bool private _initialized;
|
||||
uint16 internal _royaltyBasisPoints;
|
||||
|
||||
mapping(uint256 => FollowData) internal _followDataByFollowId;
|
||||
mapping(uint256 => uint256) internal _followIdByFollowerId;
|
||||
mapping(uint256 => uint256) internal _approvedFollowWithTokenByFollowerId;
|
||||
mapping(uint256 => address) internal _approvedSetFollowerInTokenByFollowId;
|
||||
uint256 internal _royaltiesInBasisPoints;
|
||||
|
||||
event SetFollowerInTokenApproved(uint256 indexed followId, address approved);
|
||||
event FollowWithTokenApproved(uint256 indexed follower, uint256 followId);
|
||||
@@ -76,7 +78,7 @@ contract FollowNFT is HubRestricted, LensNFTBase, IFollowNFT {
|
||||
if (_initialized) revert Errors.Initialized();
|
||||
_initialized = true;
|
||||
_followedProfileId = profileId;
|
||||
_royaltyBasisPoints = 1000; // 10% of royalties
|
||||
_setRoyalty(1000); // 10% of royalties
|
||||
emit Events.FollowNFTInitialized(profileId, block.timestamp);
|
||||
}
|
||||
|
||||
@@ -378,44 +380,32 @@ contract FollowNFT is HubRestricted, LensNFTBase, IFollowNFT {
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Changes the royalty percentage for secondary sales. Can only be called publication's
|
||||
* profile owner.
|
||||
*
|
||||
* @param royaltyBasisPoints The royalty percentage meassured in basis points. Each basis point
|
||||
* represents 0.01%.
|
||||
* @dev See {IERC165-supportsInterface}.
|
||||
*/
|
||||
// TODO: We can move this to a base contract and share logic between Follow and Collect NFTs
|
||||
function setRoyalty(uint256 royaltyBasisPoints) external {
|
||||
if (IERC721(HUB).ownerOf(_followedProfileId) == msg.sender) {
|
||||
if (royaltyBasisPoints > BASIS_POINTS) {
|
||||
revert Errors.InvalidParameter();
|
||||
} else {
|
||||
_royaltyBasisPoints = uint16(royaltyBasisPoints);
|
||||
}
|
||||
} else {
|
||||
function supportsInterface(bytes4 interfaceId)
|
||||
public
|
||||
view
|
||||
virtual
|
||||
override(ERC2981CollectionRoyalties, ERC721Enumerable)
|
||||
returns (bool)
|
||||
{
|
||||
return
|
||||
ERC2981CollectionRoyalties.supportsInterface(interfaceId) ||
|
||||
ERC721Enumerable.supportsInterface(interfaceId);
|
||||
}
|
||||
|
||||
function _getReceiver(uint256 tokenId) internal view override returns (address) {
|
||||
return IERC721(HUB).ownerOf(_followedProfileId);
|
||||
}
|
||||
|
||||
function _beforeRoyaltiesSet(uint256 royaltiesInBasisPoints) internal override {
|
||||
if (IERC721(HUB).ownerOf(_followedProfileId) != msg.sender) {
|
||||
revert Errors.NotProfileOwner();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Called with the sale price to determine how much royalty
|
||||
* is owed and to whom.
|
||||
*
|
||||
* @param followId The ID of the follow token queried for royalty information.
|
||||
* @param salePrice The sale price of the token specified.
|
||||
* @return A tuple with the address who should receive the royalties and the royalty
|
||||
* payment amount for the given sale price.
|
||||
*/
|
||||
// TODO: We can move this to a base contract and share logic between Follow and Collect NFTs
|
||||
function royaltyInfo(uint256 followId, uint256 salePrice)
|
||||
external
|
||||
view
|
||||
returns (address, uint256)
|
||||
{
|
||||
return (
|
||||
IERC721(HUB).ownerOf(_followedProfileId),
|
||||
(salePrice * _royaltyBasisPoints) / BASIS_POINTS
|
||||
);
|
||||
function _getRoyaltiesInBasisPointsSlot() internal view override returns (uint256) {
|
||||
return ROYALTIES_IN_BASIS_POINTS_SLOT;
|
||||
}
|
||||
|
||||
/// NOTE: We allow approve for unwrapped assets to, which is not supposed to be part of ERC-721.
|
||||
|
||||
85
contracts/core/base/ERC2981CollectionRoyalties.sol
Normal file
85
contracts/core/base/ERC2981CollectionRoyalties.sol
Normal file
@@ -0,0 +1,85 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import {Errors} from '../../libraries/Errors.sol';
|
||||
import {IERC165} from '@openzeppelin/contracts/utils/introspection/IERC165.sol';
|
||||
import {IERC2981} from '@openzeppelin/contracts/interfaces/IERC2981.sol';
|
||||
|
||||
abstract contract ERC2981CollectionRoyalties is IERC2981 {
|
||||
uint16 internal constant BASIS_POINTS = 10000;
|
||||
// bytes4(keccak256('royaltyInfo(uint256,uint256)')) == 0x2a55205a
|
||||
bytes4 internal constant INTERFACE_ID_ERC2981 = 0x2a55205a;
|
||||
|
||||
/**
|
||||
* @dev See {IERC165-supportsInterface}.
|
||||
*/
|
||||
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
|
||||
return interfaceId == INTERFACE_ID_ERC2981 || interfaceId == type(IERC165).interfaceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Changes the royalty percentage for secondary sales.
|
||||
*
|
||||
* @param royaltiesInBasisPoints The royalty percentage meassured in basis points.
|
||||
*/
|
||||
function setRoyalty(uint256 royaltiesInBasisPoints) external {
|
||||
_beforeRoyaltiesSet(royaltiesInBasisPoints);
|
||||
_setRoyalty(royaltiesInBasisPoints);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Called with the sale price to determine how much royalty is owed and to whom.
|
||||
*
|
||||
* @param tokenId The ID of the token queried for royalty information.
|
||||
* @param salePrice The sale price of the token specified.
|
||||
* @return A tuple with the address who should receive the royalties and the royalty
|
||||
* payment amount for the given sale price.
|
||||
*/
|
||||
function royaltyInfo(uint256 tokenId, uint256 salePrice)
|
||||
external
|
||||
view
|
||||
returns (address, uint256)
|
||||
{
|
||||
return (_getReceiver(tokenId), _getRoyaltyAmount(tokenId, salePrice));
|
||||
}
|
||||
|
||||
function _setRoyalty(uint256 royaltiesInBasisPoints) internal virtual {
|
||||
if (royaltiesInBasisPoints > BASIS_POINTS) {
|
||||
revert Errors.InvalidParameter();
|
||||
} else {
|
||||
_storeRoyaltiesInBasisPoints(royaltiesInBasisPoints);
|
||||
}
|
||||
}
|
||||
|
||||
function _getRoyaltyAmount(uint256 tokenId, uint256 salePrice)
|
||||
internal
|
||||
view
|
||||
virtual
|
||||
returns (uint256)
|
||||
{
|
||||
return (salePrice * _loadRoyaltiesInBasisPoints()) / BASIS_POINTS;
|
||||
}
|
||||
|
||||
function _storeRoyaltiesInBasisPoints(uint256 royaltiesInBasisPoints) internal virtual {
|
||||
uint256 royaltiesInBasisPointsSlot = _getRoyaltiesInBasisPointsSlot();
|
||||
assembly {
|
||||
sstore(royaltiesInBasisPointsSlot, royaltiesInBasisPoints)
|
||||
}
|
||||
}
|
||||
|
||||
function _loadRoyaltiesInBasisPoints() internal view virtual returns (uint256) {
|
||||
uint256 royaltiesInBasisPointsSlot = _getRoyaltiesInBasisPointsSlot();
|
||||
uint256 royaltyAmount;
|
||||
assembly {
|
||||
royaltyAmount := sload(royaltiesInBasisPointsSlot)
|
||||
}
|
||||
return royaltyAmount;
|
||||
}
|
||||
|
||||
function _beforeRoyaltiesSet(uint256 royaltiesInBasisPoints) internal virtual {}
|
||||
|
||||
function _getRoyaltiesInBasisPointsSlot() internal view virtual returns (uint256);
|
||||
|
||||
function _getReceiver(uint256 tokenId) internal view virtual returns (address);
|
||||
}
|
||||
Reference in New Issue
Block a user