DCIPs/assets/eip-5173/Implementation/nFR.sol

247 lines
10 KiB
Solidity

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