Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ON-813: ERC721 Marketplace - Accept Rental Offer #43

Merged
merged 6 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 58 additions & 2 deletions contracts/NftRentalMarketplace.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
pragma solidity 0.8.9;

import { IERC721 } from '@openzeppelin/contracts/token/ERC721/IERC721.sol';
import { IERC7432VaultExtension } from './interfaces/IERC7432VaultExtension.sol';
import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import { IOriumMarketplaceRoyalties } from './interfaces/IOriumMarketplaceRoyalties.sol';
import { OwnableUpgradeable } from '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol';
import { Initializable } from '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol';
import { PausableUpgradeable } from '@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol';
import { LibNftRentalMarketplace, RentalOffer } from './libraries/LibNftRentalMarketplace.sol';
import { LibNftRentalMarketplace, RentalOffer, Rental } from './libraries/LibNftRentalMarketplace.sol';

/**
* @title Orium NFT Marketplace - Marketplace for renting NFTs
Expand All @@ -30,6 +30,9 @@ contract NftRentalMarketplace is Initializable, OwnableUpgradeable, PausableUpgr
/// @dev role => tokenAddress => tokenId => deadline
mapping(bytes32 => mapping(address => mapping(uint256 => uint64))) public roleDeadline;

/// @dev hashedOffer => Rental
mapping(bytes32 => Rental) public rentals;

/** ######### Events ########### **/

/**
Expand Down Expand Up @@ -58,6 +61,14 @@ contract NftRentalMarketplace is Initializable, OwnableUpgradeable, PausableUpgr
bytes[] rolesData
);

/**
* @param lender The address of the lender
* @param nonce The nonce of the rental offer
* @param borrower The address of the borrower
* @param expirationDate The expiration date of the rental
*/
event RentalStarted(address indexed lender, uint256 indexed nonce, address indexed borrower, uint64 expirationDate);

/** ######### Initializer ########### **/
/**
* @notice Initializes the contract.
Expand Down Expand Up @@ -116,6 +127,51 @@ contract NftRentalMarketplace is Initializable, OwnableUpgradeable, PausableUpgr
);
}

/**
* @notice Accepts a rental offer.
* @dev The borrower can be address(0) to allow anyone to rent the NFT.
* @param _offer The rental offer struct. It should be the same as the one used to create the offer.
* @param _duration The duration of the rental.
*/
function acceptRentalOffer(RentalOffer calldata _offer, uint64 _duration) external whenNotPaused {
bytes32 _offerHash = LibNftRentalMarketplace.hashRentalOffer(_offer);
uint64 _expirationDate = uint64(block.timestamp + _duration);

LibNftRentalMarketplace.validateAcceptRentalOfferParams(
_offer.borrower,
_offer.minDuration,
isCreated[_offerHash],
rentals[_offerHash].expirationDate,
_duration,
nonceDeadline[_offer.lender][_offer.nonce],
_expirationDate
);

LibNftRentalMarketplace.transferFees(
_offer.feeTokenAddress,
owner(),
_offer.lender,
oriumMarketplaceRoyalties,
_offer.tokenAddress,
_offer.feeAmountPerSecond,
_duration
);

LibNftRentalMarketplace.grantRoles(
oriumMarketplaceRoyalties,
_offer.tokenAddress,
_offer.tokenId,
msg.sender,
_expirationDate,
_offer.roles,
_offer.rolesData
);

rentals[_offerHash] = Rental({ borrower: msg.sender, expirationDate: _expirationDate });

emit RentalStarted(_offer.lender, _offer.nonce, msg.sender, _expirationDate);
}

/** ============================ Core Functions ================================== **/

/** ######### Setters ########### **/
Expand Down
143 changes: 143 additions & 0 deletions contracts/libraries/LibNftRentalMarketplace.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
pragma solidity 0.8.9;

import { IERC721 } from '@openzeppelin/contracts/token/ERC721/IERC721.sol';
import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import { IERC7432 } from '../interfaces/IERC7432.sol';
import { IERC7432VaultExtension } from '../interfaces/IERC7432VaultExtension.sol';
import { IOriumMarketplaceRoyalties } from '../interfaces/IOriumMarketplaceRoyalties.sol';

Expand All @@ -21,7 +23,16 @@ struct RentalOffer {
bytes[] rolesData;
}

/// @dev Rental info.
struct Rental {
address borrower;
uint64 expirationDate;
}

library LibNftRentalMarketplace {
/// @dev 100 ether is 100%
uint256 public constant MAX_PERCENTAGE = 100 ether;

/**
* @notice Gets the rental offer hash.
* @dev This function is used to hash the rental offer struct
Expand Down Expand Up @@ -79,4 +90,136 @@ library LibNftRentalMarketplace {
);
require(_nonceDeadline == 0, 'NftRentalMarketplace: nonce already used');
}

/**
* @dev All values needs to be in wei.
* @param _amount The amount to calculate the percentage from.
* @param _percentage The percentage to calculate.
*/
function getAmountFromPercentage(uint256 _amount, uint256 _percentage) public pure returns (uint256) {
return (_amount * _percentage) / MAX_PERCENTAGE;
}

/**
* @notice Transfers the fees.
* @dev The fee token address should be approved before calling this function.
* @param _feeTokenAddress The fee token address.
* @param _marketplaceTreasuryAddress The marketplace treasury address.
* @param _lenderAddress The lender address.
* @param _oriumRoyaltiesAddress The Orium marketplace royalties contract address.
* @param _tokenAddress The token address.
* @param _feeAmountPerSecond The fee amount per second.
* @param _duration The duration of the rental.
*/
function transferFees(
address _feeTokenAddress,
address _marketplaceTreasuryAddress,
address _lenderAddress,
address _oriumRoyaltiesAddress,
address _tokenAddress,
uint256 _feeAmountPerSecond,
uint64 _duration
) external {
uint256 _totalAmount = _feeAmountPerSecond * _duration;
if (_totalAmount == 0) return;

IOriumMarketplaceRoyalties _royalties = IOriumMarketplaceRoyalties(_oriumRoyaltiesAddress);
uint256 _marketplaceFeePercentageInWei = _royalties.marketplaceFeeOf(_tokenAddress);
IOriumMarketplaceRoyalties.RoyaltyInfo memory _royaltyInfo = _royalties.royaltyInfoOf(_tokenAddress);

uint256 _marketplaceAmount = getAmountFromPercentage(_totalAmount, _marketplaceFeePercentageInWei);
uint256 _royaltyAmount = getAmountFromPercentage(_totalAmount, _royaltyInfo.royaltyPercentageInWei);
uint256 _lenderAmount = _totalAmount - _royaltyAmount - _marketplaceAmount;

_transferAmount(_feeTokenAddress, _marketplaceTreasuryAddress, _marketplaceAmount);
_transferAmount(_feeTokenAddress, _royaltyInfo.treasury, _royaltyAmount);
_transferAmount(_feeTokenAddress, _lenderAddress, _lenderAmount);
}

/**
* @notice Transfers an amount to a receipient.
* @dev This function is used to make an ERC20 transfer.
* @param _tokenAddress The token address.
* @param _to The recipient address.
* @param _amount The amount to transfer.
*/
function _transferAmount(address _tokenAddress, address _to, uint256 _amount) internal {
if (_amount == 0) return;
require(IERC20(_tokenAddress).transferFrom(msg.sender, _to, _amount), 'NftRentalMarketplace: Transfer failed');
}

/**
* @notice Validates the accept rental offer.
* @dev This function is used to validate the accept rental offer params.
* @param _borrower The borrower address
* @param _minDuration The minimum duration of the rental
* @param _isCreated The boolean value to check if the offer is created
* @param _previousRentalExpirationDate The expiration date of the previous rental
* @param _duration The duration of the rental
* @param _nonceDeadline The deadline of the nonce
* @param _expirationDate The expiration date of the rental
*/
function validateAcceptRentalOfferParams(
ernanirst marked this conversation as resolved.
Show resolved Hide resolved
address _borrower,
uint64 _minDuration,
bool _isCreated,
uint64 _previousRentalExpirationDate,
uint64 _duration,
uint256 _nonceDeadline,
uint64 _expirationDate
) external view {
require(_isCreated, 'NftRentalMarketplace: Offer not created');
require(
_previousRentalExpirationDate <= block.timestamp,
'NftRentalMarketplace: This offer has an ongoing rental'
);
require(_duration >= _minDuration, 'NftRentalMarketplace: Duration is less than the offer minimum duration');
require(
_nonceDeadline > _expirationDate,
'NftRentalMarketplace: expiration date is greater than offer deadline'
);
require(
address(0) == _borrower || msg.sender == _borrower,
'NftRentalMarketplace: Sender is not allowed to rent this NFT'
);
}

/**
* @notice Grants multiple roles to the same NFT.
* @dev This function is used to batch grant roles for the same NFT.
* @param _oriumMarketplaceRoyalties The Orium marketplace royalties contract address.
* @param _tokenAddress The token address.
* @param _tokenId The token id.
* @param _recipient The recipient address.
* @param _expirationDate The expiration date.
* @param _roleIds The role ids.
* @param _data The data.
*/
function grantRoles(
address _oriumMarketplaceRoyalties,
address _tokenAddress,
uint256 _tokenId,
address _recipient,
uint64 _expirationDate,
bytes32[] calldata _roleIds,
bytes[] calldata _data
) external {
address _rolesRegsitry = IOriumMarketplaceRoyalties(_oriumMarketplaceRoyalties).nftRolesRegistryOf(
_tokenAddress
);

for (uint256 i = 0; i < _roleIds.length; i++) {
IERC7432(_rolesRegsitry).grantRole(
IERC7432.Role({
roleId: _roleIds[i],
tokenAddress: _tokenAddress,
tokenId: _tokenId,
recipient: _recipient,
expirationDate: _expirationDate,
revocable: false,
data: _data[i]
})
);
}
}
}
Loading
Loading