diff --git a/.gitignore b/.gitignore index f831836..5df2304 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ coverage.json out cache_forge forge-artifacts +.npmrc .openzeppelin/unknown-31337.json \ No newline at end of file diff --git a/README.md b/README.md index 28a18d4..af56f1c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ Orium Protocol for renting NFTs. Powered by ERC7432 and ERC7589. - ## Getting Started To get started with this project, clone the repository and run the following commands: @@ -14,7 +13,6 @@ npm test npm run coverage ``` - ## Install Foundry To be able to use foundry, run the following commands: diff --git a/addresses/cronos/index.json b/addresses/cronos/index.json index 8af8984..a7747d8 100644 --- a/addresses/cronos/index.json +++ b/addresses/cronos/index.json @@ -1,38 +1,38 @@ { - "Multisig": { - "address": "0x04c8c6c56dab836f8bd62cb6884371507e706806" - }, - "RolesRegistry": { - "address": "0xB1b599Ec67ad23AF7FAC240191319822e674571A" - }, - "SftRolesRegistrySingleRole": { - "address": "" - }, - "KMSDeployer": { - "address": "0x04c8c6c56dab836f8bd62cb6884371507e706806" - }, - "NftRentalMarketplace": { - "address": "", - "operator": "", - "implementation": "", - "proxyAdmin": "" - }, - "ImmutableOwnerCreate2Factory": { - "address": "0x066f91a9Aa4C33D4ea4c12aBee6f4cb4e919F71d" - }, - "OriumMarketplaceRoyalties": { - "address": "0xC3154ccAC181eb9d71ccd53f29F425BDdD52d983", - "operator": "0x04c8c6c56dab836f8bd62cb6884371507e706806", - "implementation": "0xAb8E668acD4FEF935D5DfD869410bf8704688a53", - "proxyAdmin": "0x5E053177c73636d4378cfB4D095cFb374eBb3Da6" - }, - "OriumSftMarketplace": { - "address": "0x48c769f6a8de57d824f0e7330d7A27dee53a43cD", - "operator": "0x04c8c6c56dab836f8bd62cb6884371507e706806", - "implementation": "0x17ecC3620cE4062fA5eC4298a6BDc6d5A25d75Fe", - "proxyAdmin": "0x5E053177c73636d4378cfB4D095cFb374eBb3Da6" - }, - "ERC7432WrapperForERC4907": { - "address": "" - } -} \ No newline at end of file + "Multisig": { + "address": "0x04c8c6c56dab836f8bd62cb6884371507e706806" + }, + "RolesRegistry": { + "address": "0xB1b599Ec67ad23AF7FAC240191319822e674571A" + }, + "SftRolesRegistrySingleRole": { + "address": "" + }, + "KMSDeployer": { + "address": "0x04c8c6c56dab836f8bd62cb6884371507e706806" + }, + "NftRentalMarketplace": { + "address": "", + "operator": "", + "implementation": "", + "proxyAdmin": "" + }, + "ImmutableOwnerCreate2Factory": { + "address": "0x066f91a9Aa4C33D4ea4c12aBee6f4cb4e919F71d" + }, + "OriumMarketplaceRoyalties": { + "address": "0xC3154ccAC181eb9d71ccd53f29F425BDdD52d983", + "operator": "0x04c8c6c56dab836f8bd62cb6884371507e706806", + "implementation": "0xAb8E668acD4FEF935D5DfD869410bf8704688a53", + "proxyAdmin": "0x5E053177c73636d4378cfB4D095cFb374eBb3Da6" + }, + "OriumSftMarketplace": { + "address": "0x48c769f6a8de57d824f0e7330d7A27dee53a43cD", + "operator": "0x04c8c6c56dab836f8bd62cb6884371507e706806", + "implementation": "0x17ecC3620cE4062fA5eC4298a6BDc6d5A25d75Fe", + "proxyAdmin": "0x5E053177c73636d4378cfB4D095cFb374eBb3Da6" + }, + "ERC7432WrapperForERC4907": { + "address": "" + } +} diff --git a/addresses/cronosTestnet/index.json b/addresses/cronosTestnet/index.json index 1b4aa31..299a7a6 100644 --- a/addresses/cronosTestnet/index.json +++ b/addresses/cronosTestnet/index.json @@ -1,38 +1,38 @@ { - "Multisig": { - "address": "0x04c8c6c56dab836f8bd62cb6884371507e706806" - }, - "RolesRegistry": { - "address": "0xB1b599Ec67ad23AF7FAC240191319822e674571A" - }, - "SftRolesRegistrySingleRole": { - "address": "" - }, - "KMSDeployer": { - "address": "0x04c8c6c56dab836f8bd62cb6884371507e706806" - }, - "NftRentalMarketplace": { - "address": "", - "operator": "", - "implementation": "", - "proxyAdmin": "" - }, - "ImmutableOwnerCreate2Factory": { - "address": "0x066f91a9Aa4C33D4ea4c12aBee6f4cb4e919F71d" - }, - "OriumMarketplaceRoyalties": { - "address": "0x48c769f6a8de57d824f0e7330d7A27dee53a43cD", - "operator": "0x04c8c6c56dab836f8bd62cb6884371507e706806", - "implementation": "0x17ecC3620cE4062fA5eC4298a6BDc6d5A25d75Fe", - "proxyAdmin": "0xC3154ccAC181eb9d71ccd53f29F425BDdD52d983" - }, - "OriumSftMarketplace": { - "address": "0x3Af779a00868B3Ac35Ea9424340ec8A23ADDCfB4", - "operator": "0x04c8c6c56dab836f8bd62cb6884371507e706806", - "implementation": "0xe359D2353D8f2E6Bd574E49F34c987729380EA1C", - "proxyAdmin": "0xC3154ccAC181eb9d71ccd53f29F425BDdD52d983" - }, - "ERC7432WrapperForERC4907": { - "address": "" - } -} \ No newline at end of file + "Multisig": { + "address": "0x04c8c6c56dab836f8bd62cb6884371507e706806" + }, + "RolesRegistry": { + "address": "0xB1b599Ec67ad23AF7FAC240191319822e674571A" + }, + "SftRolesRegistrySingleRole": { + "address": "" + }, + "KMSDeployer": { + "address": "0x04c8c6c56dab836f8bd62cb6884371507e706806" + }, + "NftRentalMarketplace": { + "address": "", + "operator": "", + "implementation": "", + "proxyAdmin": "" + }, + "ImmutableOwnerCreate2Factory": { + "address": "0x066f91a9Aa4C33D4ea4c12aBee6f4cb4e919F71d" + }, + "OriumMarketplaceRoyalties": { + "address": "0x48c769f6a8de57d824f0e7330d7A27dee53a43cD", + "operator": "0x04c8c6c56dab836f8bd62cb6884371507e706806", + "implementation": "0x17ecC3620cE4062fA5eC4298a6BDc6d5A25d75Fe", + "proxyAdmin": "0xC3154ccAC181eb9d71ccd53f29F425BDdD52d983" + }, + "OriumSftMarketplace": { + "address": "0x3Af779a00868B3Ac35Ea9424340ec8A23ADDCfB4", + "operator": "0x04c8c6c56dab836f8bd62cb6884371507e706806", + "implementation": "0xe359D2353D8f2E6Bd574E49F34c987729380EA1C", + "proxyAdmin": "0xC3154ccAC181eb9d71ccd53f29F425BDdD52d983" + }, + "ERC7432WrapperForERC4907": { + "address": "" + } +} diff --git a/addresses/moonbeam/index.json b/addresses/moonbeam/index.json index ff71741..a081dde 100644 --- a/addresses/moonbeam/index.json +++ b/addresses/moonbeam/index.json @@ -1,44 +1,42 @@ { - "Multisig": { - "address": "" - }, - "RolesRegistry": { - "address": "" - }, - "SftRolesRegistrySingleRole": { - "address": "" - }, - "KMSDeployer": { - "address": "0x04c8c6c56dab836f8bd62cb6884371507e706806" - }, - "ImmutableOwnerCreate2Factory": { - "address": "" - }, - "OriumMarketplaceRoyalties": { - "address": "0x1FdB03ee7e0A861D0495075C1459ef87bC4de54c", - "operator": "0x04c8c6c56dab836f8bd62cb6884371507e706806", - "implementation": "0xeE485138ae7b0A50F2342c67675c6D0a3bd1d3ea", - "proxyAdmin": "0xbee720D292d591cc94f522050cc2069070e3a15C" - }, - "OriumSftMarketplace": { - "address": "", - "operator": "", - "implementation": "", - "proxyAdmin": "", - "libraries": [ - "" - ] - }, - "ERC7432WrapperForERC4907": { - "address": "0xc3154ccac181eb9d71ccd53f29f425bddd52d983" - }, - "NftRentalMarketplace": { - "address": "0x201E1636BB21Dfd51F93815BCD008EAe2Fa29bD9", - "operator": "0x04c8c6c56dab836f8bd62cb6884371507e706806", - "implementation": "0x1112949A242A0283c2170b9F28931ccBE8878d18", - "proxyAdmin": "0x668e73cF24361cfE13801681d8885e6632A7Eaa6", - "libraries": { - "LibNftRentalMarketplace": "0x037c0ae89bAE6d86476074CE3ef4F235467B1B79" - } - } -} \ No newline at end of file + "Multisig": { + "address": "" + }, + "RolesRegistry": { + "address": "" + }, + "SftRolesRegistrySingleRole": { + "address": "" + }, + "KMSDeployer": { + "address": "0x04c8c6c56dab836f8bd62cb6884371507e706806" + }, + "ImmutableOwnerCreate2Factory": { + "address": "" + }, + "OriumMarketplaceRoyalties": { + "address": "0x1FdB03ee7e0A861D0495075C1459ef87bC4de54c", + "operator": "0x04c8c6c56dab836f8bd62cb6884371507e706806", + "implementation": "0xeE485138ae7b0A50F2342c67675c6D0a3bd1d3ea", + "proxyAdmin": "0xbee720D292d591cc94f522050cc2069070e3a15C" + }, + "OriumSftMarketplace": { + "address": "", + "operator": "", + "implementation": "", + "proxyAdmin": "", + "libraries": [""] + }, + "ERC7432WrapperForERC4907": { + "address": "0xc3154ccac181eb9d71ccd53f29f425bddd52d983" + }, + "NftRentalMarketplace": { + "address": "0x201E1636BB21Dfd51F93815BCD008EAe2Fa29bD9", + "operator": "0x04c8c6c56dab836f8bd62cb6884371507e706806", + "implementation": "0x1112949A242A0283c2170b9F28931ccBE8878d18", + "proxyAdmin": "0x668e73cF24361cfE13801681d8885e6632A7Eaa6", + "libraries": { + "LibNftRentalMarketplace": "0x037c0ae89bAE6d86476074CE3ef4F235467B1B79" + } + } +} diff --git a/addresses/polygon/index.json b/addresses/polygon/index.json index ee2fb69..ade7542 100644 --- a/addresses/polygon/index.json +++ b/addresses/polygon/index.json @@ -1,41 +1,41 @@ { - "Multisig": { - "address": "0x359E1208DE02Af11461A37D72165Ef2dcD2Adfc8" - }, - "RolesRegistry": { - "address": "0xB1b599Ec67ad23AF7FAC240191319822e674571A" - }, - "SftRolesRegistrySingleRole": { - "address": "0x071BC9F5aA747A9A8E8cE796e006d10dBf241E04" - }, - "KMSDeployer": { - "address": "0x04c8c6c56dab836f8bd62cb6884371507e706806" - }, - "NftRentalMarketplace": { - "address": "", - "operator": "", - "implementation": "", - "proxyAdmin": "" - }, - "ImmutableOwnerCreate2Factory": { - "address": "0x066f91a9Aa4C33D4ea4c12aBee6f4cb4e919F71d" - }, - "OriumMarketplaceRoyalties": { - "address": "0x1fBAf746747aDdd76B70C76cd1069fFcaB1B7be4", - "operator": "0x359E1208DE02Af11461A37D72165Ef2dcD2Adfc8", - "implementation": "0x4211fC0292D38c38EdE6b0cec9E27a69f40cDf1a", - "proxyAdmin": "0x48c769f6a8de57d824f0e7330d7A27dee53a43cD" - }, - "OriumSftMarketplace": { - "address": "0xB1D47B09aa6D81d7B00C3A37705a6A157B83C49F", - "operator": "0x359E1208DE02Af11461A37D72165Ef2dcD2Adfc8", - "implementation": "0x28c0DFe3e6B53ff3A25B9655A0B9D62120Ae34Fc", - "proxyAdmin": "0x48c769f6a8de57d824f0e7330d7A27dee53a43cD", - "libraries": { - "LibOriumSftMarketplace": "0xF992Cf6815aC9c08B3631b5587e16d561ef9a969" - } - }, - "ERC7432WrapperForERC4907": { - "address": "" - } -} \ No newline at end of file + "Multisig": { + "address": "0x359E1208DE02Af11461A37D72165Ef2dcD2Adfc8" + }, + "RolesRegistry": { + "address": "0xB1b599Ec67ad23AF7FAC240191319822e674571A" + }, + "SftRolesRegistrySingleRole": { + "address": "0x071BC9F5aA747A9A8E8cE796e006d10dBf241E04" + }, + "KMSDeployer": { + "address": "0x04c8c6c56dab836f8bd62cb6884371507e706806" + }, + "NftRentalMarketplace": { + "address": "", + "operator": "", + "implementation": "", + "proxyAdmin": "" + }, + "ImmutableOwnerCreate2Factory": { + "address": "0x066f91a9Aa4C33D4ea4c12aBee6f4cb4e919F71d" + }, + "OriumMarketplaceRoyalties": { + "address": "0x1fBAf746747aDdd76B70C76cd1069fFcaB1B7be4", + "operator": "0x359E1208DE02Af11461A37D72165Ef2dcD2Adfc8", + "implementation": "0x4211fC0292D38c38EdE6b0cec9E27a69f40cDf1a", + "proxyAdmin": "0x48c769f6a8de57d824f0e7330d7A27dee53a43cD" + }, + "OriumSftMarketplace": { + "address": "0xB1D47B09aa6D81d7B00C3A37705a6A157B83C49F", + "operator": "0x359E1208DE02Af11461A37D72165Ef2dcD2Adfc8", + "implementation": "0x28c0DFe3e6B53ff3A25B9655A0B9D62120Ae34Fc", + "proxyAdmin": "0x48c769f6a8de57d824f0e7330d7A27dee53a43cD", + "libraries": { + "LibOriumSftMarketplace": "0xF992Cf6815aC9c08B3631b5587e16d561ef9a969" + } + }, + "ERC7432WrapperForERC4907": { + "address": "" + } +} diff --git a/contracts/NftRentalMarketplace.sol b/contracts/NftRentalMarketplace.sol index 3662120..e9edf8e 100644 --- a/contracts/NftRentalMarketplace.sol +++ b/contracts/NftRentalMarketplace.sol @@ -106,6 +106,7 @@ contract NftRentalMarketplace is Initializable, OwnableUpgradeable, PausableUpgr * @dev To optimize for gas, only the offer hash is stored on-chain * @param _offer The rental offer struct. */ + function createRentalOffer(RentalOffer calldata _offer) external whenNotPaused { LibNftRentalMarketplace.validateCreateRentalOfferParams( oriumMarketplaceRoyalties, @@ -118,7 +119,7 @@ contract NftRentalMarketplace is Initializable, OwnableUpgradeable, PausableUpgr roleDeadline[_offer.roles[i]][_offer.tokenAddress][_offer.tokenId] < block.timestamp, 'NftRentalMarketplace: role still has an active offer' ); - roleDeadline[_offer.roles[i]][_offer.tokenAddress][_offer.tokenId] = _offer.deadline; + roleDeadline[_offer.roles[i]][_offer.tokenAddress][_offer.tokenId] = _offer.deadline - _offer.minDuration; } bytes32 _offerHash = LibNftRentalMarketplace.hashRentalOffer(_offer); @@ -149,7 +150,6 @@ contract NftRentalMarketplace is Initializable, OwnableUpgradeable, PausableUpgr 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, @@ -180,6 +180,10 @@ contract NftRentalMarketplace is Initializable, OwnableUpgradeable, PausableUpgr _offer.rolesData ); + // for (uint256 i = 0; i < _offer.roles.length; i++) { + // roleDeadline[_offer.roles[i]][_offer.tokenAddress][_offer.tokenId] - _expirationDate; + // } + rentals[_offerHash] = Rental({ borrower: msg.sender, expirationDate: _expirationDate }); emit RentalStarted(_offer.lender, _offer.nonce, msg.sender, _expirationDate); diff --git a/contracts/OriumMarketplaceRoyalties.sol b/contracts/OriumMarketplaceRoyalties.sol index e81cea6..0216000 100644 --- a/contracts/OriumMarketplaceRoyalties.sol +++ b/contracts/OriumMarketplaceRoyalties.sol @@ -2,9 +2,9 @@ pragma solidity 0.8.9; -import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.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 { IOriumMarketplaceRoyalties } from './interfaces/IOriumMarketplaceRoyalties.sol'; /** * @title Orium Marketplace Royalties @@ -124,7 +124,7 @@ contract OriumMarketplaceRoyalties is Initializable, OwnableUpgradeable, IOriumM uint256 _royaltyPercentage = tokenAddressToRoyaltyInfo[_tokenAddress].royaltyPercentageInWei; require( _royaltyPercentage + _feePercentageInWei < MAX_PERCENTAGE, - "OriumMarketplaceRoyalties: Royalty percentage + marketplace fee cannot be greater than 100%" + 'OriumMarketplaceRoyalties: Royalty percentage + marketplace fee cannot be greater than 100%' ); feeInfo[_tokenAddress] = FeeInfo({ feePercentageInWei: _feePercentageInWei, isCustomFee: _isCustomFee }); @@ -149,14 +149,14 @@ contract OriumMarketplaceRoyalties is Initializable, OwnableUpgradeable, IOriumM if (msg.sender != owner()) { require( msg.sender == tokenAddressToRoyaltyInfo[_tokenAddress].creator, - "OriumMarketplaceRoyalties: Only creator or owner can set the royalty info" + 'OriumMarketplaceRoyalties: Only creator or owner can set the royalty info' ); - require(msg.sender == _creator, "OriumMarketplaceRoyalties: sender and creator mismatch"); + require(msg.sender == _creator, 'OriumMarketplaceRoyalties: sender and creator mismatch'); } require( _royaltyPercentageInWei + marketplaceFeeOf(_tokenAddress) < MAX_PERCENTAGE, - "OriumMarketplaceRoyalties: Royalty percentage + marketplace fee cannot be greater than 100%" + 'OriumMarketplaceRoyalties: Royalty percentage + marketplace fee cannot be greater than 100%' ); tokenAddressToRoyaltyInfo[_tokenAddress] = RoyaltyInfo({ @@ -174,7 +174,7 @@ contract OriumMarketplaceRoyalties is Initializable, OwnableUpgradeable, IOriumM * @param _maxDuration The maximum duration of a rental offer. */ function setMaxDuration(uint64 _maxDuration) external onlyOwner { - require(_maxDuration > 0, "OriumMarketplaceRoyalties: Max duration should be greater than 0"); + require(_maxDuration > 0, 'OriumMarketplaceRoyalties: Max duration should be greater than 0'); maxDuration = _maxDuration; } @@ -209,7 +209,7 @@ contract OriumMarketplaceRoyalties is Initializable, OwnableUpgradeable, IOriumM /** * @notice Sets the trusted fee token addresses for a token. - * @dev Can only be called by the owner. + * @dev Can only be called by the owner. * @param _tokenAddresses The NFT or SFT addresses. * @param _feeTokenAddresses The fee token addresses. * @param _isTrusted The boolean array. @@ -221,7 +221,7 @@ contract OriumMarketplaceRoyalties is Initializable, OwnableUpgradeable, IOriumM ) external onlyOwner { require( _tokenAddresses.length == _feeTokenAddresses.length && _tokenAddresses.length == _isTrusted.length, - "OriumMarketplaceRoyalties: Arrays should have the same length" + 'OriumMarketplaceRoyalties: Arrays should have the same length' ); for (uint256 i = 0; i < _tokenAddresses.length; i++) { isTrustedFeeTokenAddressForToken[_tokenAddresses[i]][_feeTokenAddresses[i]] = _isTrusted[i]; diff --git a/contracts/interfaces/IERC7589.sol b/contracts/interfaces/IERC7589.sol index 202a306..f77cd05 100644 --- a/contracts/interfaces/IERC7589.sol +++ b/contracts/interfaces/IERC7589.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.9; -import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +import { IERC165 } from '@openzeppelin/contracts/utils/introspection/IERC165.sol'; interface IERC7589 is IERC165 { struct RoleAssignment { diff --git a/contracts/interfaces/IOriumMarketplaceRoyalties.sol b/contracts/interfaces/IOriumMarketplaceRoyalties.sol index d1a99cc..9f5905c 100644 --- a/contracts/interfaces/IOriumMarketplaceRoyalties.sol +++ b/contracts/interfaces/IOriumMarketplaceRoyalties.sol @@ -42,7 +42,10 @@ interface IOriumMarketplaceRoyalties { * @param _tokenAddress The SFT or NFT address. * @param _feeTokenAddress The fee token address. */ - function isTrustedFeeTokenAddressForToken(address _tokenAddress, address _feeTokenAddress) external view returns (bool); + function isTrustedFeeTokenAddressForToken( + address _tokenAddress, + address _feeTokenAddress + ) external view returns (bool); /** * @notice Gets the royalty info. diff --git a/contracts/libraries/LibNftRentalMarketplace.sol b/contracts/libraries/LibNftRentalMarketplace.sol index 418adeb..0f43dd9 100644 --- a/contracts/libraries/LibNftRentalMarketplace.sol +++ b/contracts/libraries/LibNftRentalMarketplace.sol @@ -307,8 +307,7 @@ library LibNftRentalMarketplace { ); require( msg.sender == IERC721(_params[i].tokenAddress).ownerOf(_params[i].tokenId) || - msg.sender == - IERC7432(_rolesRegistry).ownerOf(_params[i].tokenAddress, _params[i].tokenId), + msg.sender == IERC7432(_rolesRegistry).ownerOf(_params[i].tokenAddress, _params[i].tokenId), 'OriumNftMarketplace: sender is not the owner' ); diff --git a/contracts/libraries/LibOriumSftMarketplace.sol b/contracts/libraries/LibOriumSftMarketplace.sol index 9acc682..c0d46c4 100644 --- a/contracts/libraries/LibOriumSftMarketplace.sol +++ b/contracts/libraries/LibOriumSftMarketplace.sol @@ -2,9 +2,9 @@ pragma solidity 0.8.9; -import { IERC7589 } from "../interfaces/IERC7589.sol"; -import { IOriumMarketplaceRoyalties } from "../interfaces/IOriumMarketplaceRoyalties.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC7589 } from '../interfaces/IERC7589.sol'; +import { IOriumMarketplaceRoyalties } from '../interfaces/IOriumMarketplaceRoyalties.sol'; +import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; /// @dev Rental offer info. struct RentalOffer { @@ -49,7 +49,7 @@ library LibOriumSftMarketplace { * @param _offer The rental offer struct to be hashed. */ function hashRentalOffer(RentalOffer memory _offer) external pure returns (bytes32) { - return + return _offer.minDuration == 0 ? keccak256( abi.encode( @@ -103,7 +103,7 @@ library LibOriumSftMarketplace { ); require( _rolesRegistry.grantorOf(_commitmentId) == _expectedGrantor, - "OriumSftMarketplace: expected grantor does not match the grantor of the commitmentId" + 'OriumSftMarketplace: expected grantor does not match the grantor of the commitmentId' ); require( _rolesRegistry.tokenAddressOf(_commitmentId) == _tokenAddress, @@ -120,19 +120,19 @@ library LibOriumSftMarketplace { * @param _offer The rental offer struct to be validated. */ function validateOffer(RentalOffer memory _offer) external view { - require(_offer.tokenAmount > 0, "OriumSftMarketplace: tokenAmount should be greater than 0"); - require(_offer.nonce != 0, "OriumSftMarketplace: Nonce cannot be 0"); - require(msg.sender == _offer.lender, "OriumSftMarketplace: Sender and Lender mismatch"); - require(_offer.roles.length > 0, "OriumSftMarketplace: roles should not be empty"); + require(_offer.tokenAmount > 0, 'OriumSftMarketplace: tokenAmount should be greater than 0'); + require(_offer.nonce != 0, 'OriumSftMarketplace: Nonce cannot be 0'); + require(msg.sender == _offer.lender, 'OriumSftMarketplace: Sender and Lender mismatch'); + require(_offer.roles.length > 0, 'OriumSftMarketplace: roles should not be empty'); require( _offer.roles.length == _offer.rolesData.length, - "OriumSftMarketplace: roles and rolesData should have the same length" + 'OriumSftMarketplace: roles and rolesData should have the same length' ); require( _offer.borrower != address(0) || _offer.feeAmountPerSecond > 0, - "OriumSftMarketplace: feeAmountPerSecond should be greater than 0" + 'OriumSftMarketplace: feeAmountPerSecond should be greater than 0' ); - require(_offer.minDuration <= _offer.deadline - block.timestamp, "OriumSftMarketplace: minDuration is invalid"); + require(_offer.minDuration <= _offer.deadline - block.timestamp, 'OriumSftMarketplace: minDuration is invalid'); } /** @@ -158,20 +158,20 @@ library LibOriumSftMarketplace { if (_marketplaceFeeAmount > 0) { require( IERC20(_feeTokenAddress).transferFrom(msg.sender, _marketplaceTreasuryAddress, _marketplaceFeeAmount), - "OriumSftMarketplace: Transfer failed" + 'OriumSftMarketplace: Transfer failed' ); } if (_royaltyAmount > 0) { require( IERC20(_feeTokenAddress).transferFrom(msg.sender, _royaltyTreasuryAddress, _royaltyAmount), - "OriumSftMarketplace: Transfer failed" + 'OriumSftMarketplace: Transfer failed' ); } require( IERC20(_feeTokenAddress).transferFrom(msg.sender, _lenderAddress, _lenderAmount), - "OriumSftMarketplace: Transfer failed" + 'OriumSftMarketplace: Transfer failed' ); } @@ -187,7 +187,7 @@ library LibOriumSftMarketplace { address[] calldata _tokenAddresses, uint256[] calldata _commitmentIds ) external { - require(_tokenAddresses.length == _commitmentIds.length, "OriumSftMarketplace: arrays length mismatch"); + require(_tokenAddresses.length == _commitmentIds.length, 'OriumSftMarketplace: arrays length mismatch'); for (uint256 i = 0; i < _tokenAddresses.length; i++) { address _rolesRegistryAddress = IOriumMarketplaceRoyalties(_oriumMarketplaceRoyaltiesAddress) .sftRolesRegistryOf(_tokenAddresses[i]); @@ -222,7 +222,7 @@ library LibOriumSftMarketplace { _commitmentIds.length == _roles.length && _commitmentIds.length == _grantees.length && _commitmentIds.length == _tokenAddresses.length, - "OriumSftMarketplace: arrays length mismatch" + 'OriumSftMarketplace: arrays length mismatch' ); for (uint256 i = 0; i < _commitmentIds.length; i++) { @@ -231,14 +231,16 @@ library LibOriumSftMarketplace { ); require( IERC7589(_rolesRegistryAddress).isRoleRevocable(_commitmentIds[i], _roles[i], _grantees[i]), - "OriumSftMarketplace: role is not revocable" + 'OriumSftMarketplace: role is not revocable' ); require( - IERC7589(_rolesRegistryAddress).roleExpirationDate(_commitmentIds[i], _roles[i], _grantees[i]) > block.timestamp, - "OriumSftMarketplace: role is expired" + IERC7589(_rolesRegistryAddress).roleExpirationDate(_commitmentIds[i], _roles[i], _grantees[i]) > + block.timestamp, + 'OriumSftMarketplace: role is expired' ); require( - msg.sender == _grantees[i] || IERC7589(_rolesRegistryAddress).grantorOf(_commitmentIds[i]) == msg.sender, + msg.sender == _grantees[i] || + IERC7589(_rolesRegistryAddress).grantorOf(_commitmentIds[i]) == msg.sender, "OriumSftMarketplace: sender is not the commitment's grantor or grantee" ); require( @@ -260,11 +262,11 @@ library LibOriumSftMarketplace { uint64 _expirationDate ) external view { require(_isCreated, 'OriumSftMarketplace: Offer not created'); - require(_previousRentalExpirationDate <= block.timestamp, 'OriumSftMarketplace: This offer has an ongoing rental'); require( - _duration >= _minDuration, - 'OriumSftMarketplace: Duration is less than the offer minimum duration' + _previousRentalExpirationDate <= block.timestamp, + 'OriumSftMarketplace: This offer has an ongoing rental' ); + require(_duration >= _minDuration, 'OriumSftMarketplace: Duration is less than the offer minimum duration'); require( _nonceDeadline > _expirationDate, 'OriumSftMarketplace: expiration date is greater than offer deadline' diff --git a/contracts/mocks/MockERC1155.sol b/contracts/mocks/MockERC1155.sol index 6286ad8..0f23077 100644 --- a/contracts/mocks/MockERC1155.sol +++ b/contracts/mocks/MockERC1155.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.9; -import { ERC1155 } from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; +import { ERC1155 } from '@openzeppelin/contracts/token/ERC1155/ERC1155.sol'; /** * @title MockERC1155 @@ -10,7 +10,7 @@ import { ERC1155 } from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; */ contract MockERC1155 is ERC1155 { - constructor() ERC1155("") {} + constructor() ERC1155('') {} function mint(address to, uint256 tokenId, uint256 amount, bytes memory data) external { _mint(to, tokenId, amount, data); diff --git a/contracts/mocks/MockERC20.sol b/contracts/mocks/MockERC20.sol index e2d971a..f4dab15 100644 --- a/contracts/mocks/MockERC20.sol +++ b/contracts/mocks/MockERC20.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.9; -import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import { ERC20 } from '@openzeppelin/contracts/token/ERC20/ERC20.sol'; /** * @title MockERC20 @@ -13,7 +13,7 @@ contract MockERC20 is ERC20 { bool public revertTransfer; uint256 public revertTransferCount; - constructor() ERC20("PaymentToken", "PAY") {} + constructor() ERC20('PaymentToken', 'PAY') {} function mint(address to, uint256 amount) external { _mint(to, amount); diff --git a/contracts/mocks/MockERC721.sol b/contracts/mocks/MockERC721.sol index 08ed4cf..5b15578 100644 --- a/contracts/mocks/MockERC721.sol +++ b/contracts/mocks/MockERC721.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.9; -import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import { ERC721 } from '@openzeppelin/contracts/token/ERC721/ERC721.sol'; /** * @title MockERC721 @@ -10,7 +10,7 @@ import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; */ contract MockERC721 is ERC721 { - constructor() ERC721("MockNft", "MOCK") {} + constructor() ERC721('MockNft', 'MOCK') {} function mint(address to, uint256 tokenId) external { _mint(to, tokenId); diff --git a/hardhat.config.ts b/hardhat.config.ts index 2384386..035cd7b 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -38,7 +38,7 @@ const BASE_CONFIG = { goerli: ETHER_SCAN_API_KEY, cronosTestnet: CRONOSSCAN_API_KEY, cronos: CRONOSSCAN_API_KEY, - moonbeam: MOONSCAN_API_KEY, + //moonbeam: MOONSCAN_API_KEY, }, customChains: [ { @@ -108,11 +108,11 @@ const PROD_CONFIG = { url: CRONOS_PROVIDER_URL, accounts: [PROD_PRIVATE_KEY], }, - moonbeam: { - chainId: 1284, - url: MOONBEAM_PROVIDER_URL, - accounts: [DEV_PRIVATE_KEY], - }, + // moonbeam: { + // chainId: 1284, + // url: MOONBEAM_PROVIDER_URL, + // accounts: [DEV_PRIVATE_KEY], + // }, }, defender: { apiKey: DEFENDER_TEAM_API_KEY, diff --git a/package-lock.json b/package-lock.json index f56782d..7202454 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "orium-rental-protocol", + "name": "orium-rental-marketplace-contracts", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/scripts/orium-sft-marketplace/data/offers.md b/scripts/orium-sft-marketplace/data/offers.md index ce46c79..a100f8c 100644 --- a/scripts/orium-sft-marketplace/data/offers.md +++ b/scripts/orium-sft-marketplace/data/offers.md @@ -14,4 +14,4 @@ BORROWED (PUBLIC OFFER) Wearable 252 (helmet) 0xb1d47b09aa6d81d7b00c3a37705a6a157b83c49f-0xe3a75c99cd21674188bea652fe378ca5cf7e7906-74225167615946612830948158755332947701235130917797518061907650999220496639193 BORROWED (PRIVATE OFFER) Werable 350 (t-shirt) -0xb1d47b09aa6d81d7b00c3a37705a6a157b83c49f-0xe3a75c99cd21674188bea652fe378ca5cf7e7906-88503925159079469072461611536684263787861271681407587493144538997301870344226 \ No newline at end of file +0xb1d47b09aa6d81d7b00c3a37705a6a157b83c49f-0xe3a75c99cd21674188bea652fe378ca5cf7e7906-88503925159079469072461611536684263787861271681407587493144538997301870344226 diff --git a/test/NftRentalMarketplace.test.ts b/test/NftRentalMarketplace.test.ts index 7d948b8..8268666 100644 --- a/test/NftRentalMarketplace.test.ts +++ b/test/NftRentalMarketplace.test.ts @@ -4,7 +4,15 @@ import { loadFixture, time } from '@nomicfoundation/hardhat-network-helpers' import { expect } from 'chai' import { toWei } from '../utils/bignumber' import { GrantRoleParams, RentalOffer, RoyaltyInfo } from '../utils/types' -import { AddressZero, EMPTY_BYTES, ONE_DAY, ONE_HOUR, THREE_MONTHS } from '../utils/constants' +import { + AddressZero, + EMPTY_BYTES, + ONE_DAY, + ONE_HOUR, + THREE_MONTHS, + TWENTY_THREE_HOURS, + TEN_MINUTES, +} from '../utils/constants' import { randomBytes } from 'crypto' import { UNIQUE_ROLE, USER_ROLE } from '../utils/roles' import { @@ -163,6 +171,28 @@ describe('NftRentalMarketplace', () => { rentalOffer.rolesData, ) }) + it('Should create more than one rental offer for the same role, if the (Deadline - minduration) is reached ', async () => { + rentalOffer.minDuration = TWENTY_THREE_HOURS + await marketplace.connect(lender).createRentalOffer(rentalOffer) + await time.increase(ONE_HOUR) + rentalOffer.deadline = Number(await time.latest()) + ONE_DAY + rentalOffer.nonce = `0x${randomBytes(32).toString('hex')}` + await expect(marketplace.connect(lender).createRentalOffer(rentalOffer)) + .to.emit(marketplace, 'RentalOfferCreated') + .withArgs( + rentalOffer.nonce, + rentalOffer.tokenAddress, + rentalOffer.tokenId, + rentalOffer.lender, + rentalOffer.borrower, + rentalOffer.feeTokenAddress, + rentalOffer.feeAmountPerSecond, + rentalOffer.deadline, + rentalOffer.minDuration, + rentalOffer.roles, + rentalOffer.rolesData, + ) + }) it('Should create rental offer if token is already deposited in rolesRegistry', async function () { await mockERC721.connect(lender).approve(await rolesRegistry.getAddress(), tokenId) await rolesRegistry.connect(lender).grantRole({ @@ -191,6 +221,7 @@ describe('NftRentalMarketplace', () => { rentalOffer.rolesData, ) }) + it('Should NOT create a rental offer if contract is paused', async () => { await marketplace.connect(operator).pause() await expect(marketplace.connect(lender).createRentalOffer(rentalOffer)).to.be.revertedWith( @@ -272,6 +303,17 @@ describe('NftRentalMarketplace', () => { 'NftRentalMarketplace: role still has an active offer', ) }) + it('Should NOT create more than one rental when the (Deadline - minduration) is not be reached. ', async () => { + rentalOffer.nonce = `0x${randomBytes(32).toString('hex')}` + rentalOffer.minDuration = TWENTY_THREE_HOURS + await marketplace.connect(lender).createRentalOffer(rentalOffer) + await time.increase(TEN_MINUTES) + + rentalOffer.nonce = `0x${randomBytes(32).toString('hex')}` + await expect(marketplace.connect(lender).createRentalOffer(rentalOffer)).to.be.revertedWith( + 'NftRentalMarketplace: role still has an active offer', + ) + }) }) }) diff --git a/test/foundry/OriumSftMarketplaceTest.sol b/test/foundry/OriumSftMarketplaceTest.sol index ae0e8a2..3fd0f3b 100644 --- a/test/foundry/OriumSftMarketplaceTest.sol +++ b/test/foundry/OriumSftMarketplaceTest.sol @@ -2,8 +2,8 @@ pragma solidity 0.8.9; -import { SetupTest } from "./SetupTest.sol"; -import { RentalOffer } from "../../contracts/libraries/LibOriumSftMarketplace.sol"; +import { SetupTest } from './SetupTest.sol'; +import { RentalOffer } from '../../contracts/libraries/LibOriumSftMarketplace.sol'; contract OriumSftMarketplaceTest is SetupTest { uint256 nonceCounter; @@ -21,7 +21,7 @@ contract OriumSftMarketplaceTest is SetupTest { roles[0] = UNIQUE_ROLE; bytes[] memory rolesData = new bytes[](1); - rolesData[0] = "0x"; + rolesData[0] = '0x'; RentalOffer memory _offer = RentalOffer({ lender: lenderFuzz, @@ -38,7 +38,7 @@ contract OriumSftMarketplaceTest is SetupTest { rolesData: rolesData }); - sft.mint(lenderFuzz, tokenId, tokenAmount, ""); + sft.mint(lenderFuzz, tokenId, tokenAmount, ''); vm.startPrank(lenderFuzz); sft.setApprovalForAll(address(rolesRegistry), true); rolesRegistry.setRoleApprovalForAll(address(sft), address(marketplace), true); @@ -46,7 +46,7 @@ contract OriumSftMarketplaceTest is SetupTest { vm.stopPrank(); } - function test_fuzz_acceptRentalOffer(address lenderFuzz, address borrowerFuzz, uint64 duration) public { + function test_fuzz_acceptRentalOffer(address lenderFuzz, address borrowerFuzz, uint64 duration) public { vm.assume(lenderFuzz != address(0) && lenderFuzz.code.length == 0); vm.assume(borrowerFuzz != address(0) && borrowerFuzz.code.length == 0); uint64 deadline = uint64(block.timestamp + 30 days); @@ -56,7 +56,7 @@ contract OriumSftMarketplaceTest is SetupTest { roles[0] = UNIQUE_ROLE; bytes[] memory rolesData = new bytes[](1); - rolesData[0] = "0x"; + rolesData[0] = '0x'; RentalOffer memory _offer = RentalOffer({ lender: lenderFuzz, @@ -74,7 +74,7 @@ contract OriumSftMarketplaceTest is SetupTest { }); vm.startPrank(lenderFuzz); - sft.mint(lenderFuzz, tokenId, tokenAmount, ""); + sft.mint(lenderFuzz, tokenId, tokenAmount, ''); sft.setApprovalForAll(address(rolesRegistry), true); rolesRegistry.setRoleApprovalForAll(address(sft), address(marketplace), true); marketplace.createRentalOffer(_offer); @@ -94,7 +94,7 @@ contract OriumSftMarketplaceTest is SetupTest { roles[0] = UNIQUE_ROLE; bytes[] memory rolesData = new bytes[](1); - rolesData[0] = "0x"; + rolesData[0] = '0x'; RentalOffer memory _offer = RentalOffer({ lender: lenderFuzz, @@ -112,7 +112,7 @@ contract OriumSftMarketplaceTest is SetupTest { }); vm.startPrank(lenderFuzz); - sft.mint(lenderFuzz, tokenId, tokenAmount, ""); + sft.mint(lenderFuzz, tokenId, tokenAmount, ''); sft.setApprovalForAll(address(rolesRegistry), true); rolesRegistry.setRoleApprovalForAll(address(sft), address(marketplace), true); marketplace.createRentalOffer(_offer); @@ -129,7 +129,7 @@ contract OriumSftMarketplaceTest is SetupTest { roles[0] = UNIQUE_ROLE; bytes[] memory rolesData = new bytes[](1); - rolesData[0] = "0x"; + rolesData[0] = '0x'; RentalOffer memory _offer = RentalOffer({ lender: lender, @@ -146,7 +146,7 @@ contract OriumSftMarketplaceTest is SetupTest { rolesData: rolesData }); - sft.mint(lender, tokenId, tokenAmount, ""); + sft.mint(lender, tokenId, tokenAmount, ''); vm.startPrank(lender); sft.setApprovalForAll(address(rolesRegistry), true); rolesRegistry.setRoleApprovalForAll(address(sft), address(marketplace), true); diff --git a/test/foundry/SetupTest.sol b/test/foundry/SetupTest.sol index 1933ac9..662033f 100644 --- a/test/foundry/SetupTest.sol +++ b/test/foundry/SetupTest.sol @@ -2,12 +2,12 @@ pragma solidity 0.8.9; -import { Test } from "../../lib/forge-std/src/Test.sol"; -import { OriumSftMarketplace } from "../../contracts/OriumSftMarketplace.sol"; -import { OriumMarketplaceRoyalties } from "../../contracts/OriumMarketplaceRoyalties.sol"; -import { SftRolesRegistrySingleRole } from "../../contracts/mocks/SftRolesRegistrySingleRole.sol"; -import { MockERC1155 } from "../../contracts/mocks/MockERC1155.sol"; -import { MockERC20 } from "../../contracts/mocks/MockERC20.sol"; +import { Test } from '../../lib/forge-std/src/Test.sol'; +import { OriumSftMarketplace } from '../../contracts/OriumSftMarketplace.sol'; +import { OriumMarketplaceRoyalties } from '../../contracts/OriumMarketplaceRoyalties.sol'; +import { SftRolesRegistrySingleRole } from '../../contracts/mocks/SftRolesRegistrySingleRole.sol'; +import { MockERC1155 } from '../../contracts/mocks/MockERC1155.sol'; +import { MockERC20 } from '../../contracts/mocks/MockERC20.sol'; contract SetupTest is Test { OriumSftMarketplace public marketplace; @@ -15,7 +15,7 @@ contract SetupTest is Test { SftRolesRegistrySingleRole public rolesRegistry; MockERC1155 public sft; MockERC20 public feeToken; - + uint64 public constant MAX_DURATION = 90 days; uint256 public constant tokenId = 1; uint256 public constant tokenAmount = 1; @@ -51,8 +51,8 @@ contract SetupTest is Test { _trustedFeeTokens[0] = address(feeToken); address[] memory _trustedNftTokens = new address[](1); _trustedNftTokens[0] = address(sft); - - sft.mint(address(lender), tokenId, tokenAmount, ""); + + sft.mint(address(lender), tokenId, tokenAmount, ''); royalties.setTrustedNftTokens(_trustedNftTokens, _isTrusted); royalties.setTrustedFeeTokens(_trustedFeeTokens, _isTrusted); royalties.setRolesRegistry(address(sft), address(rolesRegistry)); diff --git a/utils/constants.ts b/utils/constants.ts index 03ac99a..f6c0a66 100644 --- a/utils/constants.ts +++ b/utils/constants.ts @@ -1,8 +1,10 @@ import { ethers } from 'hardhat' export const RolesRegistryAddress = '0xB2aD616e84e1eF7A9ED0fA3169AAF31Ee51EA824' // RolesRegistry has the same address on all networks +export const TEN_MINUTES = 10 * 60 export const ONE_DAY = 60 * 60 * 24 export const ONE_HOUR = 60 * 60 +export const TWENTY_THREE_HOURS = 23 * 60 * 60 export const THREE_MONTHS = 60 * 60 * 24 * 30 * 3 export const EMPTY_BYTES = '0x' export const MAX_PERCENTAGE = ethers.parseEther('100')