Skip to content

Commit

Permalink
Merge pull request #43 from OriumNetwork/feature--ON-813
Browse files Browse the repository at this point in the history
ON-813: ERC721 Marketplace - Accept Rental Offer
  • Loading branch information
karacurt authored Apr 22, 2024
2 parents 9f7ae60 + b5ae053 commit 2f5f30d
Show file tree
Hide file tree
Showing 4 changed files with 437 additions and 9 deletions.
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(
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

0 comments on commit 2f5f30d

Please sign in to comment.