247 lines
10 KiB
Solidity
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;
|
|
}
|
|
}
|
|
|
|
} |