// SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.0; import "./InFR.sol"; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; import "@openzeppelin/contracts/utils/math/SafeMath.sol"; import "@prb/math/contracts/PRBMathUD60x18.sol"; import "@prb/math/contracts/PRBMathSD59x18.sol"; import "hardhat/console.sol"; abstract contract nFR is InFR, ERC721 { using Address for address; using PRBMathUD60x18 for uint256; using PRBMathSD59x18 for int256; struct FRInfo { uint8 numGenerations; // Number of generations corresponding to that Token ID uint256 percentOfProfit; // Percent of profit allocated for FR, scaled by 1e18 uint256 successiveRatio; // The common ratio of successive in the geometric sequence, used for distribution calculation uint256 lastSoldPrice; // Last sale price in ETH mantissa uint256 ownerAmount; // Amount of owners the Token ID has seen bool isValid; // Updated by contract and signifies if an FR Info for a given Token ID is valid } struct ListInfo { uint256 salePrice; // ETH mantissa of the listed selling price address lister; // Owner/Lister of the Token bool isListed; // Boolean indicating whether the Token is listed or not } FRInfo private _defaultFRInfo; // Takes Token ID and returns corresponding FR Info mapping(uint256 => FRInfo) private _tokenFRInfo; // Takes Token ID and returns the addresses currently in the FR cycle mapping(uint256 => address[]) private _addressesInFR; // Takes Address and returns amount of ether available to address from FR payments mapping(address => uint256) private _allottedFR; // Takes Token ID and returns corresponding ListInfo mapping(uint256 => ListInfo) private _tokenListInfo; function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) { return interfaceId == type(InFR).interfaceId || super.supportsInterface(interfaceId); } function retrieveFRInfo(uint256 tokenId) public view virtual override returns(uint8 numGenerations, uint256 percentOfProfit, uint256 successiveRatio, uint256 lastSoldPrice, uint256 ownerAmount, address[] memory addressesInFR) { return (_tokenFRInfo[tokenId].numGenerations, _tokenFRInfo[tokenId].percentOfProfit, _tokenFRInfo[tokenId].successiveRatio, _tokenFRInfo[tokenId].lastSoldPrice, _tokenFRInfo[tokenId].ownerAmount, _addressesInFR[tokenId]); } function retrieveListInfo(uint256 tokenId) public view virtual override returns(uint256, address, bool) { return (_tokenListInfo[tokenId].salePrice, _tokenListInfo[tokenId].lister, _tokenListInfo[tokenId].isListed); } function retrieveAllottedFR(address account) public view virtual override returns(uint256) { return _allottedFR[account]; } function _transferFrom(address from, address to, uint256 tokenId, uint256 soldPrice) internal virtual { ERC721._transfer(from, to, tokenId); require(_checkERC721Received(from, to, tokenId, ""), "ERC721: transfer to non ERC721Receiver implementer"); if (soldPrice <= _tokenFRInfo[tokenId].lastSoldPrice) { // NFT sold for a loss, meaning no FR distribution, but we still shift generations, and update price. We return ALL of the received ETH to the msg.sender as no FR chunk was needed. _tokenFRInfo[tokenId].lastSoldPrice = soldPrice; _tokenFRInfo[tokenId].ownerAmount++; _shiftGenerations(to, tokenId); (bool sent, ) = payable(_tokenListInfo[tokenId].lister).call{value: soldPrice}(""); require(sent, "ERC5173: Failed to send msg.value to lister"); } else { _distributeFR(tokenId, soldPrice); _tokenFRInfo[tokenId].lastSoldPrice = soldPrice; _tokenFRInfo[tokenId].ownerAmount++; _shiftGenerations(to, tokenId); } delete _tokenListInfo[tokenId]; } function list(uint256 tokenId, uint256 salePrice) public virtual override { require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC5173: list caller is not owner nor approved"); _tokenListInfo[tokenId] = ListInfo(salePrice, _msgSender(), true); } function unlist(uint256 tokenId) public virtual override { require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC5173: unlist caller is not owner nor approved"); delete _tokenListInfo[tokenId]; } function buy(uint256 tokenId) public virtual override payable { require(_tokenListInfo[tokenId].isListed == true, "Token is not listed"); require(_tokenListInfo[tokenId].salePrice == msg.value, "salePrice and msg.value mismatch"); _transferFrom(_tokenListInfo[tokenId].lister, _msgSender(), tokenId, _tokenListInfo[tokenId].salePrice); } function _transfer(address from, address to, uint256 tokenId) internal virtual override { super._transfer(from, to, tokenId); if (_tokenListInfo[tokenId].isListed == true) { delete _tokenListInfo[tokenId]; } _tokenFRInfo[tokenId].lastSoldPrice = 0; _tokenFRInfo[tokenId].ownerAmount++; _shiftGenerations(to, tokenId); } function _mint(address to, uint256 tokenId) internal virtual override { require(_defaultFRInfo.isValid, "No Default FR Info has been set"); super._mint(to, tokenId); _tokenFRInfo[tokenId] = FRInfo(_defaultFRInfo.numGenerations, _defaultFRInfo.percentOfProfit, _defaultFRInfo.successiveRatio, 0, 1, true); _addressesInFR[tokenId].push(to); } function _burn(uint256 tokenId) internal virtual override { super._burn(tokenId); delete _tokenFRInfo[tokenId]; delete _addressesInFR[tokenId]; delete _tokenListInfo[tokenId]; } function _mint(address to, uint256 tokenId, uint8 numGenerations, uint256 percentOfProfit, uint256 successiveRatio) internal virtual { require(numGenerations > 0 && percentOfProfit > 0 && percentOfProfit <= 1e18 && successiveRatio > 0, "Invalid Data Passed"); ERC721._mint(to, tokenId); require(_checkERC721Received(address(0), to, tokenId, ""), "ERC721: transfer to non ERC721Receiver implementer"); _tokenFRInfo[tokenId] = FRInfo(numGenerations, percentOfProfit, successiveRatio, 0, 1, true); _addressesInFR[tokenId].push(to); } function _distributeFR(uint256 tokenId, uint256 soldPrice) internal virtual { uint256 profit = soldPrice - _tokenFRInfo[tokenId].lastSoldPrice; uint256[] memory FR = _calculateFR(profit, _tokenFRInfo[tokenId].percentOfProfit, _tokenFRInfo[tokenId].successiveRatio, _tokenFRInfo[tokenId].ownerAmount, _tokenFRInfo[tokenId].numGenerations); for (uint owner = 0; owner < FR.length; owner++) { _allottedFR[_addressesInFR[tokenId][owner]] += FR[owner]; } uint256 allocatedFR = 0; for (uint reward = 0; reward < FR.length; reward++) { allocatedFR += FR[reward]; } (bool sent, ) = payable(_tokenListInfo[tokenId].lister).call{value: soldPrice - allocatedFR}(""); require(sent, "Failed to send ETH after FR distribution to lister"); emit FRDistributed(tokenId, soldPrice, allocatedFR); } function _shiftGenerations(address to, uint256 tokenId) internal virtual { if (_addressesInFR[tokenId].length < _tokenFRInfo[tokenId].numGenerations) { // We just want to push to the array _addressesInFR[tokenId].push(to); } else { // We want to remove the first element in the array and then push to the end of the array for (uint i = 0; i < _addressesInFR[tokenId].length-1; i++) { _addressesInFR[tokenId][i] = _addressesInFR[tokenId][i+1]; } _addressesInFR[tokenId].pop(); _addressesInFR[tokenId].push(to); } } function _setDefaultFRInfo(uint8 numGenerations, uint256 percentOfProfit, uint256 successiveRatio) internal virtual { require(numGenerations > 0 && percentOfProfit > 0 && percentOfProfit <= 1e18 && successiveRatio > 0, "Invalid Data Passed"); _defaultFRInfo.numGenerations = numGenerations; _defaultFRInfo.percentOfProfit = percentOfProfit; _defaultFRInfo.successiveRatio = successiveRatio; _defaultFRInfo.isValid = true; } function releaseFR(address payable account) public virtual override { require(_allottedFR[account] > 0, "No FR Payment due"); uint256 FRAmount = _allottedFR[account]; _allottedFR[account] = 0; (bool sent, ) = account.call{value: FRAmount}(""); require(sent, "Failed to release FR"); emit FRClaimed(account, FRAmount); } function _calculateFR(uint256 totalProfit, uint256 buyerReward, uint256 successiveRatio, uint256 ownerAmount, uint256 windowSize) pure internal virtual returns(uint256[] memory) { uint256 n = Math.min(ownerAmount, windowSize); uint256[] memory FR = new uint256[](n); for (uint256 i = 1; i < n + 1; i++) { uint256 pi = 0; if (successiveRatio != 1e18) { int256 v1 = 1e18 - int256(successiveRatio).powu(n); int256 v2 = int256(successiveRatio).powu(i - 1); int256 v3 = int256(totalProfit).mul(int256(buyerReward)); int256 v4 = v3.mul(1e18 - int256(successiveRatio)); pi = uint256(v4 * v2 / v1); } else { pi = totalProfit.mul(buyerReward).div(n); } FR[n - i] = pi; } return FR; } function _checkERC721Received( address from, address to, uint256 tokenId, bytes memory _data ) private returns (bool) { if (to.isContract()) { try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data) returns (bytes4 retval) { return retval == IERC721Receiver.onERC721Received.selector; } catch (bytes memory reason) { if (reason.length == 0) { revert("ERC721: transfer to non ERC721Receiver implementer"); } else { assembly { revert(add(32, reason), mload(reason)) } } } } else { return true; } } }