diff --git a/contracts/OriumSftMarketplace.sol b/contracts/OriumSftMarketplace.sol index 41fb470..a787ee7 100644 --- a/contracts/OriumSftMarketplace.sol +++ b/contracts/OriumSftMarketplace.sol @@ -5,6 +5,7 @@ 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 { PausableUpgradeable } from '@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol'; +import { ReentrancyGuardUpgradeable } from '@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol'; import { IERC1155 } from '@openzeppelin/contracts/token/ERC1155/IERC1155.sol'; import { IERC7589 } from './interfaces/IERC7589.sol'; import { IERC7589Legacy } from './interfaces/IERC7589Legacy.sol'; @@ -16,7 +17,7 @@ import { IOriumMarketplaceRoyalties } from './interfaces/IOriumMarketplaceRoyalt * @dev This contract is used to manage SFTs rentals, powered by ERC-7589 Semi-Fungible Token Roles * @author Orium Network Team - developers@orium.network */ -contract OriumSftMarketplace is Initializable, OwnableUpgradeable, PausableUpgradeable { +contract OriumSftMarketplace is Initializable, OwnableUpgradeable, PausableUpgradeable, ReentrancyGuardUpgradeable { /** ######### Global Variables ########### **/ /// @dev oriumMarketplaceRoyalties stores the collection royalties and fees @@ -110,6 +111,7 @@ contract OriumSftMarketplace is Initializable, OwnableUpgradeable, PausableUpgra function initialize(address _owner, address _oriumMarketplaceRoyalties) public initializer { __Pausable_init(); __Ownable_init(); + __ReentrancyGuard_init(); oriumMarketplaceRoyalties = _oriumMarketplaceRoyalties; @@ -124,7 +126,6 @@ contract OriumSftMarketplace is Initializable, OwnableUpgradeable, PausableUpgra * @dev To optimize for gas, only the offer hash is stored on-chain * @param _offer The rental offer struct. */ - function createRentalOffer(RentalOffer memory _offer) external whenNotPaused { address _rolesRegistryAddress = IOriumMarketplaceRoyalties(oriumMarketplaceRoyalties).sftRolesRegistryOf( _offer.tokenAddress @@ -168,8 +169,10 @@ contract OriumSftMarketplace is Initializable, OwnableUpgradeable, PausableUpgra * @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 { + function acceptRentalOffer( + RentalOffer calldata _offer, + uint64 _duration + ) external payable whenNotPaused nonReentrant { bytes32 _offerHash = LibOriumSftMarketplace.hashRentalOffer(_offer); uint64 _expirationDate = uint64(block.timestamp + _duration); LibOriumSftMarketplace.validateAcceptRentalOffer( @@ -441,15 +444,23 @@ contract OriumSftMarketplace is Initializable, OwnableUpgradeable, PausableUpgra ); uint256 _lenderAmount = _feeAmount - _royaltyAmount - _marketplaceFeeAmount; - LibOriumSftMarketplace.transferFees( - _feeTokenAddress, - _marketplaceFeeAmount, - _royaltyAmount, - _lenderAmount, - owner(), - _royaltyInfo.treasury, - _lenderAddress - ); + // Check if the fee token address is zero address (i.e., native token) + if (_feeTokenAddress == address(0)) { + require(msg.value == _feeAmount, 'OriumSftMarketplace: Incorrect native token amount'); + payable(owner()).transfer(_marketplaceFeeAmount); + payable(_royaltyInfo.treasury).transfer(_royaltyAmount); + payable(_lenderAddress).transfer(_lenderAmount); + } else { + LibOriumSftMarketplace.transferFees( + _feeTokenAddress, + _marketplaceFeeAmount, + _royaltyAmount, + _lenderAmount, + owner(), + _royaltyInfo.treasury, + _lenderAddress + ); + } } /** @@ -496,5 +507,6 @@ contract OriumSftMarketplace is Initializable, OwnableUpgradeable, PausableUpgra function setOriumMarketplaceRoyalties(address _oriumMarketplaceRoyalties) external onlyOwner { oriumMarketplaceRoyalties = _oriumMarketplaceRoyalties; } + /** ######### Getters ########### **/ } diff --git a/test/OriumSftMarketplace.test.ts b/test/OriumSftMarketplace.test.ts index 1533daf..f0c5f68 100644 --- a/test/OriumSftMarketplace.test.ts +++ b/test/OriumSftMarketplace.test.ts @@ -1,4 +1,5 @@ -/* eslint-disable no-unexpected-multiline */ +// SPDX-License-Identifier: CC0-1.0 + import { ethers } from 'hardhat' import { loadFixture, time } from '@nomicfoundation/hardhat-network-helpers' import { deploySftMarketplaceContracts } from './fixtures/OriumSftMarketplaceFixture' @@ -143,6 +144,7 @@ describe('OriumSftMarketplace', () => { .connect(lender) .setApprovalForAll(await SftRolesRegistrySingleRoleLegacy.getAddress(), true) }) + describe('When Rental Offer is not created', async () => { describe('Create Rental Offer', async () => { describe("When commitmentId doesn't exist", async () => { @@ -674,6 +676,43 @@ describe('OriumSftMarketplace', () => { marketplace.connect(borrower).acceptRentalOffer(rentalOffer, maxDuration), ).to.be.revertedWith('OriumSftMarketplace: expiration date is greater than offer deadline') }) + // New test case for accepting rental offer with native tokens + it('Should accept a rental offer with native tokens', async () => { + await marketplaceRoyalties + .connect(operator) + .setTrustedFeeTokenForToken([rentalOffer.tokenAddress], [AddressZero], [true]) + + rentalOffer.feeTokenAddress = AddressZero + rentalOffer.feeAmountPerSecond = toWei('0.0000001') + totalFeeAmount = rentalOffer.feeAmountPerSecond * BigInt(duration) + rentalOffer.nonce = `0x${randomBytes(32).toString('hex')}` + await marketplace.connect(lender).createRentalOffer({ ...rentalOffer, commitmentId: BigInt(0) }) + rentalOffer.commitmentId = BigInt(2) + + // Check the borrower's balance before the transaction + const borrowerBalanceBefore = await ethers.provider.getBalance(borrower.address) + console.log('BEFORE') + console.log(borrowerBalanceBefore) + + const blockTimestamp = (await ethers.provider.getBlock('latest'))?.timestamp + const expirationDate = Number(blockTimestamp) + duration + 1 + + await expect( + marketplace.connect(borrower).acceptRentalOffer(rentalOffer, duration, { + value: totalFeeAmount.toString(), + }), + ) + .to.emit(marketplace, 'RentalStarted') + .withArgs(rentalOffer.lender, rentalOffer.nonce, borrower.address, expirationDate) + + // Check the borrower's balance after the transaction + const borrowerBalanceAfter = await ethers.provider.getBalance(borrower.address) + expect(borrowerBalanceAfter).to.be.lt(borrowerBalanceBefore) + + console.log('AFTER') + console.log(borrowerBalanceAfter) + }) + describe('Fees', async function () { const feeAmountPerSecond = toWei('1') const feeAmount = feeAmountPerSecond * BigInt(duration)