feat: ERC2981CollectionRoyalties added

This commit is contained in:
donosonaumczuk
2022-11-24 17:38:22 +00:00
parent 03a992c27c
commit 0934b1356d
3 changed files with 138 additions and 85 deletions

View File

@@ -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;
}
/**

View File

@@ -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.

View 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);
}