From e23cd0d0424a48434f44971d675bdf18ef9aaf4e Mon Sep 17 00:00:00 2001 From: Eduardo Melo Date: Mon, 5 Aug 2024 13:50:11 -0300 Subject: [PATCH 01/15] feat: Accept offer to pay with native token --- contracts/OriumSftMarketplace.sol | 38 ++++++++++++++++++---------- test/OriumSftMarketplace.test.ts | 41 ++++++++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 14 deletions(-) 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) From 6190a9e26429763cbe5f72820c0e2ee850fc0219 Mon Sep 17 00:00:00 2001 From: Eduardo Melo Date: Mon, 5 Aug 2024 18:29:08 -0300 Subject: [PATCH 02/15] fix: changing require --- contracts/OriumSftMarketplace.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/OriumSftMarketplace.sol b/contracts/OriumSftMarketplace.sol index a787ee7..36d0fa6 100644 --- a/contracts/OriumSftMarketplace.sol +++ b/contracts/OriumSftMarketplace.sol @@ -444,9 +444,8 @@ contract OriumSftMarketplace is Initializable, OwnableUpgradeable, PausableUpgra ); uint256 _lenderAmount = _feeAmount - _royaltyAmount - _marketplaceFeeAmount; - // 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'); + require(msg.value >= _feeAmount, 'OriumSftMarketplace: Insufficient native token amount'); payable(owner()).transfer(_marketplaceFeeAmount); payable(_royaltyInfo.treasury).transfer(_royaltyAmount); payable(_lenderAddress).transfer(_lenderAmount); From 938e11b7f6aa8c35800e20d50ceb9fe6358ea7c5 Mon Sep 17 00:00:00 2001 From: Eduardo Melo Date: Wed, 7 Aug 2024 14:56:45 -0300 Subject: [PATCH 03/15] feat: adding reentrancy ERC-721 --- contracts/NftRentalMarketplace.sol | 58 ++++++++++++++++++++++-------- test/NftRentalMarketplace.test.ts | 48 +++++++++++++++++++++++++ test/OriumSftMarketplace.test.ts | 29 +++++++++------ 3 files changed, 110 insertions(+), 25 deletions(-) diff --git a/contracts/NftRentalMarketplace.sol b/contracts/NftRentalMarketplace.sol index cf94158..c81059f 100644 --- a/contracts/NftRentalMarketplace.sol +++ b/contracts/NftRentalMarketplace.sol @@ -9,6 +9,7 @@ import { IOriumMarketplaceRoyalties } from './interfaces/IOriumMarketplaceRoyalt 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 { LibNftRentalMarketplace, RentalOffer, Rental } from './libraries/LibNftRentalMarketplace.sol'; /** @@ -16,7 +17,7 @@ import { LibNftRentalMarketplace, RentalOffer, Rental } from './libraries/LibNft * @dev This contract is used to manage NFTs rentals, powered by ERC-7432 Non-Fungible Token Roles * @author Orium Network Team - developers@orium.network */ -contract NftRentalMarketplace is Initializable, OwnableUpgradeable, PausableUpgradeable { +contract NftRentalMarketplace is Initializable, OwnableUpgradeable, PausableUpgradeable, ReentrancyGuardUpgradeable { /** ######### Global Variables ########### **/ /// @dev oriumMarketplaceRoyalties stores the collection royalties and fees @@ -92,6 +93,7 @@ contract NftRentalMarketplace is Initializable, OwnableUpgradeable, PausableUpgr function initialize(address _owner, address _oriumMarketplaceRoyalties) external initializer { __Pausable_init(); __Ownable_init(); + __ReentrancyGuard_init(); oriumMarketplaceRoyalties = _oriumMarketplaceRoyalties; @@ -146,7 +148,10 @@ contract NftRentalMarketplace is Initializable, OwnableUpgradeable, PausableUpgr * @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 = LibNftRentalMarketplace.hashRentalOffer(_offer); uint64 _expirationDate = uint64(block.timestamp + _duration); LibNftRentalMarketplace.validateAcceptRentalOfferParams( @@ -159,15 +164,38 @@ contract NftRentalMarketplace is Initializable, OwnableUpgradeable, PausableUpgr _expirationDate ); - LibNftRentalMarketplace.transferFees( - _offer.feeTokenAddress, - owner(), - _offer.lender, - oriumMarketplaceRoyalties, - _offer.tokenAddress, - _offer.feeAmountPerSecond, - _duration - ); + if (_offer.feeTokenAddress == address(0)) { + uint256 totalFeeAmount = _offer.feeAmountPerSecond * _duration; + require(msg.value >= totalFeeAmount, 'NftRentalMarketplace: Incorrect native token amount'); + + uint256 marketplaceFeeAmount = LibNftRentalMarketplace.getAmountFromPercentage( + totalFeeAmount, + IOriumMarketplaceRoyalties(oriumMarketplaceRoyalties).marketplaceFeeOf(_offer.tokenAddress) + ); + IOriumMarketplaceRoyalties.RoyaltyInfo memory royaltyInfo = IOriumMarketplaceRoyalties( + oriumMarketplaceRoyalties + ).royaltyInfoOf(_offer.tokenAddress); + + uint256 royaltyAmount = LibNftRentalMarketplace.getAmountFromPercentage( + totalFeeAmount, + royaltyInfo.royaltyPercentageInWei + ); + uint256 lenderAmount = totalFeeAmount - marketplaceFeeAmount - royaltyAmount; + + payable(owner()).transfer(marketplaceFeeAmount); + payable(royaltyInfo.treasury).transfer(royaltyAmount); + payable(_offer.lender).transfer(lenderAmount); + } else { + LibNftRentalMarketplace.transferFees( + _offer.feeTokenAddress, + owner(), + _offer.lender, + oriumMarketplaceRoyalties, + _offer.tokenAddress, + _offer.feeAmountPerSecond, + _duration + ); + } LibNftRentalMarketplace.grantRoles( oriumMarketplaceRoyalties, @@ -180,8 +208,8 @@ contract NftRentalMarketplace is Initializable, OwnableUpgradeable, PausableUpgr ); for (uint256 i = 0; i < _offer.roles.length; i++) { - if(_expirationDate > roleDeadline[_offer.roles[i]][_offer.tokenAddress][_offer.tokenId]) { - roleDeadline[_offer.roles[i]][_offer.tokenAddress][_offer.tokenId] = _expirationDate; + if (_expirationDate > roleDeadline[_offer.roles[i]][_offer.tokenAddress][_offer.tokenId]) { + roleDeadline[_offer.roles[i]][_offer.tokenAddress][_offer.tokenId] = _expirationDate; } } @@ -233,7 +261,7 @@ contract NftRentalMarketplace is Initializable, OwnableUpgradeable, PausableUpgr _offer.tokenId, _offer.roles ); - + uint64 _offerDeadline = nonceDeadline[_offer.lender][_offer.nonce]; if (_offerDeadline < uint64(block.timestamp)) { for (uint256 i = 0; i < _offer.roles.length; i++) { @@ -268,6 +296,7 @@ contract NftRentalMarketplace is Initializable, OwnableUpgradeable, PausableUpgr * @dev owner will be msg.sender and it must approve the marketplace to revoke the roles. * @param _tokenAddresses The array of tokenAddresses * @param _tokenIds The array of tokenIds + * @param _roleIds The array of roleIds */ function batchRevokeRole( address[] memory _tokenAddresses, @@ -294,7 +323,6 @@ contract NftRentalMarketplace is Initializable, OwnableUpgradeable, PausableUpgr nonceDeadline[msg.sender][_offer.nonce] = uint64(block.timestamp); for (uint256 i = 0; i < _offer.roles.length; i++) { - if (rentals[_offerHash].expirationDate > uint64(block.timestamp)) { roleDeadline[_offer.roles[i]][_offer.tokenAddress][_offer.tokenId] = rentals[_offerHash].expirationDate; } else { diff --git a/test/NftRentalMarketplace.test.ts b/test/NftRentalMarketplace.test.ts index 0677f83..c4446d5 100644 --- a/test/NftRentalMarketplace.test.ts +++ b/test/NftRentalMarketplace.test.ts @@ -358,6 +358,7 @@ describe('NftRentalMarketplace', () => { .to.emit(marketplace, 'RentalStarted') .withArgs(rentalOffer.lender, rentalOffer.nonce, borrower.address, expirationDate) }) + it('Should accept a rental offer more than once', async () => { const rentalExpirationDate1 = Number(await time.latest()) + duration + 1 @@ -451,6 +452,50 @@ describe('NftRentalMarketplace', () => { marketplace.connect(notOperator).acceptRentalOffer(rentalOffer, duration), ).to.be.revertedWith('NftRentalMarketplace: Sender is not allowed to rent this NFT') }) + it('Should revert when accepting a rental offer with insufficient native tokens', async () => { + await time.increase(ONE_DAY) + rentalOffer.nonce = `0x${randomBytes(32).toString('hex')}` + rentalOffer.borrower = borrower.address + rentalOffer.deadline = Number(await time.latest()) + ONE_DAY + rentalOffer.feeTokenAddress = AddressZero + rentalOffer.feeAmountPerSecond = toWei('0.0000001') + await marketplaceRoyalties + .connect(operator) + .setTrustedFeeTokenForToken([rentalOffer.tokenAddress], [AddressZero], [true]) + await marketplace.connect(lender).createRentalOffer(rentalOffer) + + const totalFeeAmount = rentalOffer.feeAmountPerSecond * BigInt(duration) + + const insufficientAmount = totalFeeAmount - toWei('0.00000001') // slightly less than required + await expect( + marketplace.connect(borrower).acceptRentalOffer(rentalOffer, duration, { + value: insufficientAmount.toString(), + }), + ).to.be.revertedWith('NftRentalMarketplace: Incorrect native token amount') + }) + it('Should accept a rental offer with native tokens', async () => { + await time.increase(ONE_DAY) + rentalOffer.nonce = `0x${randomBytes(32).toString('hex')}` + rentalOffer.borrower = borrower.address + rentalOffer.deadline = Number(await time.latest()) + ONE_DAY + rentalOffer.feeTokenAddress = AddressZero + rentalOffer.feeAmountPerSecond = toWei('0.0000001') + await marketplaceRoyalties + .connect(operator) + .setTrustedFeeTokenForToken([rentalOffer.tokenAddress], [AddressZero], [true]) + await marketplace.connect(lender).createRentalOffer(rentalOffer) + + const blockTimestamp = await time.latest() + const expirationDate = 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) + }) it('Should NOT accept a rental offer if offer is expired', async () => { // move foward in time to expire the offer const blockTimestamp = await time.latest() @@ -473,6 +518,7 @@ describe('NftRentalMarketplace', () => { marketplace.connect(borrower).acceptRentalOffer(rentalOffer, maxDuration), ).to.be.revertedWith('NftRentalMarketplace: expiration date is greater than offer deadline') }) + describe('Fees', async function () { const feeAmountPerSecond = toWei('1') const feeAmount = feeAmountPerSecond * BigInt(duration) @@ -495,6 +541,7 @@ describe('NftRentalMarketplace', () => { .withArgs(rentalOffer.lender, rentalOffer.nonce, borrower.address, expirationDate) .to.emit(mockERC20, 'Transfer') }) + it('Should accept a rental offer if marketplace fee is 0', async () => { await marketplaceRoyalties .connect(operator) @@ -504,6 +551,7 @@ describe('NftRentalMarketplace', () => { 'RentalStarted', ) }) + it('Should accept a rental offer if royalty fee is 0', async () => { await marketplaceRoyalties .connect(creator) diff --git a/test/OriumSftMarketplace.test.ts b/test/OriumSftMarketplace.test.ts index f0c5f68..9595c4a 100644 --- a/test/OriumSftMarketplace.test.ts +++ b/test/OriumSftMarketplace.test.ts @@ -16,6 +16,7 @@ import { OriumSftMarketplace, SftRolesRegistrySingleRole, SftRolesRegistrySingleRoleLegacy, + ReentrancyAttack, } from '../typechain-types' describe('OriumSftMarketplace', () => { @@ -689,11 +690,6 @@ describe('OriumSftMarketplace', () => { 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 @@ -704,13 +700,26 @@ describe('OriumSftMarketplace', () => { ) .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) + it('Should revert when accepting a rental offer with insufficient native tokens', async function () { + await marketplaceRoyalties + .connect(operator) + .setTrustedFeeTokenForToken([rentalOffer.tokenAddress], [AddressZero], [true]) - console.log('AFTER') - console.log(borrowerBalanceAfter) + rentalOffer.feeTokenAddress = AddressZero + rentalOffer.feeAmountPerSecond = toWei('0.0000001') + const 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) + + const insufficientAmount = totalFeeAmount - BigInt(toWei('0.00000001')) // slightly less than required + await expect( + marketplace.connect(borrower).acceptRentalOffer(rentalOffer, BigInt(duration), { + value: insufficientAmount.toString(), + }), + ).to.be.revertedWith('OriumSftMarketplace: Insufficient native token amount') }) describe('Fees', async function () { From 0f115d175fa332aaedcae33e702f896e6b978daa Mon Sep 17 00:00:00 2001 From: Eduardo Melo Date: Mon, 12 Aug 2024 17:10:53 -0300 Subject: [PATCH 04/15] fix: adding reinitializer(2) to init the ReentrancyGuard_init --- contracts/NftRentalMarketplace.sol | 90 +++++++++++++++++++----------- contracts/OriumSftMarketplace.sol | 11 +++- test/NftRentalMarketplace.test.ts | 4 ++ test/OriumSftMarketplace.test.ts | 5 +- 4 files changed, 74 insertions(+), 36 deletions(-) diff --git a/contracts/NftRentalMarketplace.sol b/contracts/NftRentalMarketplace.sol index c81059f..e5d59a7 100644 --- a/contracts/NftRentalMarketplace.sol +++ b/contracts/NftRentalMarketplace.sol @@ -93,13 +93,20 @@ contract NftRentalMarketplace is Initializable, OwnableUpgradeable, PausableUpgr function initialize(address _owner, address _oriumMarketplaceRoyalties) external initializer { __Pausable_init(); __Ownable_init(); - __ReentrancyGuard_init(); oriumMarketplaceRoyalties = _oriumMarketplaceRoyalties; transferOwnership(_owner); } + /** + * @notice Initializes the reentrancy guard for the new version. + * @dev This function can only be called once, after the contract upgrade. + */ + function initializeV2() external reinitializer(2) { + __ReentrancyGuard_init(); + } + /** ============================ Rental Functions ================================== **/ /** ######### Setters ########### **/ @@ -164,38 +171,7 @@ contract NftRentalMarketplace is Initializable, OwnableUpgradeable, PausableUpgr _expirationDate ); - if (_offer.feeTokenAddress == address(0)) { - uint256 totalFeeAmount = _offer.feeAmountPerSecond * _duration; - require(msg.value >= totalFeeAmount, 'NftRentalMarketplace: Incorrect native token amount'); - - uint256 marketplaceFeeAmount = LibNftRentalMarketplace.getAmountFromPercentage( - totalFeeAmount, - IOriumMarketplaceRoyalties(oriumMarketplaceRoyalties).marketplaceFeeOf(_offer.tokenAddress) - ); - IOriumMarketplaceRoyalties.RoyaltyInfo memory royaltyInfo = IOriumMarketplaceRoyalties( - oriumMarketplaceRoyalties - ).royaltyInfoOf(_offer.tokenAddress); - - uint256 royaltyAmount = LibNftRentalMarketplace.getAmountFromPercentage( - totalFeeAmount, - royaltyInfo.royaltyPercentageInWei - ); - uint256 lenderAmount = totalFeeAmount - marketplaceFeeAmount - royaltyAmount; - - payable(owner()).transfer(marketplaceFeeAmount); - payable(royaltyInfo.treasury).transfer(royaltyAmount); - payable(_offer.lender).transfer(lenderAmount); - } else { - LibNftRentalMarketplace.transferFees( - _offer.feeTokenAddress, - owner(), - _offer.lender, - oriumMarketplaceRoyalties, - _offer.tokenAddress, - _offer.feeAmountPerSecond, - _duration - ); - } + _transferFees(_offer.tokenAddress, _offer.feeTokenAddress, _offer.feeAmountPerSecond, _duration, _offer.lender); LibNftRentalMarketplace.grantRoles( oriumMarketplaceRoyalties, @@ -332,6 +308,54 @@ contract NftRentalMarketplace is Initializable, OwnableUpgradeable, PausableUpgr emit RentalOfferCancelled(_offer.lender, _offer.nonce); } + /** + * @dev Transfers the fees to the marketplace, the creator and the lender. + * @param _feeTokenAddress The address of the ERC20 token for rental fees. + * @param _feeAmountPerSecond The amount of fee per second. + * @param _duration The duration of the rental. + * @param _lenderAddress The address of the lender. + */ + function _transferFees( + address _tokenAddress, + address _feeTokenAddress, + uint256 _feeAmountPerSecond, + uint64 _duration, + address _lenderAddress + ) internal { + if (_feeTokenAddress == address(0)) { + uint256 totalFeeAmount = _feeAmountPerSecond * _duration; + require(msg.value >= totalFeeAmount, 'NftRentalMarketplace: Incorrect native token amount'); + + uint256 marketplaceFeeAmount = LibNftRentalMarketplace.getAmountFromPercentage( + totalFeeAmount, + IOriumMarketplaceRoyalties(oriumMarketplaceRoyalties).marketplaceFeeOf(_tokenAddress) + ); + IOriumMarketplaceRoyalties.RoyaltyInfo memory royaltyInfo = IOriumMarketplaceRoyalties( + oriumMarketplaceRoyalties + ).royaltyInfoOf(_tokenAddress); + + uint256 royaltyAmount = LibNftRentalMarketplace.getAmountFromPercentage( + totalFeeAmount, + royaltyInfo.royaltyPercentageInWei + ); + uint256 lenderAmount = totalFeeAmount - marketplaceFeeAmount - royaltyAmount; + + payable(owner()).transfer(marketplaceFeeAmount); + payable(royaltyInfo.treasury).transfer(royaltyAmount); + payable(_lenderAddress).transfer(lenderAmount); + } else { + LibNftRentalMarketplace.transferFees( + _feeTokenAddress, + owner(), + _lenderAddress, + oriumMarketplaceRoyalties, + _tokenAddress, + _feeAmountPerSecond, + _duration + ); + } + } + /** ============================ Core Functions ================================== **/ /** ######### Setters ########### **/ diff --git a/contracts/OriumSftMarketplace.sol b/contracts/OriumSftMarketplace.sol index 36d0fa6..2a0dea7 100644 --- a/contracts/OriumSftMarketplace.sol +++ b/contracts/OriumSftMarketplace.sol @@ -111,13 +111,20 @@ contract OriumSftMarketplace is Initializable, OwnableUpgradeable, PausableUpgra function initialize(address _owner, address _oriumMarketplaceRoyalties) public initializer { __Pausable_init(); __Ownable_init(); - __ReentrancyGuard_init(); oriumMarketplaceRoyalties = _oriumMarketplaceRoyalties; transferOwnership(_owner); } + /** + * @notice Initializes the reentrancy guard for the new version. + * @dev This function can only be called once, after the contract upgrade. + */ + function initializeV2() external reinitializer(2) { + __ReentrancyGuard_init(); + } + /** ============================ Rental Functions ================================== **/ /** ######### Setters ########### **/ @@ -445,7 +452,7 @@ contract OriumSftMarketplace is Initializable, OwnableUpgradeable, PausableUpgra uint256 _lenderAmount = _feeAmount - _royaltyAmount - _marketplaceFeeAmount; if (_feeTokenAddress == address(0)) { - require(msg.value >= _feeAmount, 'OriumSftMarketplace: Insufficient native token amount'); + require(msg.value == _feeAmount, 'OriumSftMarketplace: Insufficient native token amount'); payable(owner()).transfer(_marketplaceFeeAmount); payable(_royaltyInfo.treasury).transfer(_royaltyAmount); payable(_lenderAddress).transfer(_lenderAmount); diff --git a/test/NftRentalMarketplace.test.ts b/test/NftRentalMarketplace.test.ts index c4446d5..a8faaf4 100644 --- a/test/NftRentalMarketplace.test.ts +++ b/test/NftRentalMarketplace.test.ts @@ -1049,6 +1049,10 @@ describe('NftRentalMarketplace', () => { 'Initializable: contract is already initialized', ) }) + it('Should NOT initialize the contract with initializeV2 if already initialized', async () => { + await marketplace.initializeV2() + await expect(marketplace.initializeV2()).to.be.revertedWith('Initializable: contract is already initialized') + }) }) describe('Pausable', async () => { describe('Pause', async () => { diff --git a/test/OriumSftMarketplace.test.ts b/test/OriumSftMarketplace.test.ts index 9595c4a..1d28940 100644 --- a/test/OriumSftMarketplace.test.ts +++ b/test/OriumSftMarketplace.test.ts @@ -16,7 +16,6 @@ import { OriumSftMarketplace, SftRolesRegistrySingleRole, SftRolesRegistrySingleRoleLegacy, - ReentrancyAttack, } from '../typechain-types' describe('OriumSftMarketplace', () => { @@ -1609,6 +1608,10 @@ describe('OriumSftMarketplace', () => { 'Initializable: contract is already initialized', ) }) + it('Should NOT initialize the contract with initializeV2 if already initialized', async () => { + await marketplace.initializeV2() + await expect(marketplace.initializeV2()).to.be.revertedWith('Initializable: contract is already initialized') + }) }) describe('Pausable', async () => { From e5c211dc0e4be578ab9b17a202151c7efd32bcc4 Mon Sep 17 00:00:00 2001 From: Eduardo Melo Date: Mon, 12 Aug 2024 23:08:10 -0300 Subject: [PATCH 05/15] fix: removing nooReentrancy --- contracts/NftRentalMarketplace.sol | 13 ++----------- contracts/OriumSftMarketplace.sol | 13 ++----------- test/NftRentalMarketplace.test.ts | 4 ---- test/OriumSftMarketplace.test.ts | 4 ---- 4 files changed, 4 insertions(+), 30 deletions(-) diff --git a/contracts/NftRentalMarketplace.sol b/contracts/NftRentalMarketplace.sol index e5d59a7..378bcf8 100644 --- a/contracts/NftRentalMarketplace.sol +++ b/contracts/NftRentalMarketplace.sol @@ -9,7 +9,6 @@ import { IOriumMarketplaceRoyalties } from './interfaces/IOriumMarketplaceRoyalt 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 { LibNftRentalMarketplace, RentalOffer, Rental } from './libraries/LibNftRentalMarketplace.sol'; /** @@ -17,7 +16,7 @@ import { LibNftRentalMarketplace, RentalOffer, Rental } from './libraries/LibNft * @dev This contract is used to manage NFTs rentals, powered by ERC-7432 Non-Fungible Token Roles * @author Orium Network Team - developers@orium.network */ -contract NftRentalMarketplace is Initializable, OwnableUpgradeable, PausableUpgradeable, ReentrancyGuardUpgradeable { +contract NftRentalMarketplace is Initializable, OwnableUpgradeable, PausableUpgradeable { /** ######### Global Variables ########### **/ /// @dev oriumMarketplaceRoyalties stores the collection royalties and fees @@ -99,14 +98,6 @@ contract NftRentalMarketplace is Initializable, OwnableUpgradeable, PausableUpgr transferOwnership(_owner); } - /** - * @notice Initializes the reentrancy guard for the new version. - * @dev This function can only be called once, after the contract upgrade. - */ - function initializeV2() external reinitializer(2) { - __ReentrancyGuard_init(); - } - /** ============================ Rental Functions ================================== **/ /** ######### Setters ########### **/ @@ -158,7 +149,7 @@ contract NftRentalMarketplace is Initializable, OwnableUpgradeable, PausableUpgr function acceptRentalOffer( RentalOffer calldata _offer, uint64 _duration - ) external payable whenNotPaused nonReentrant { + ) external payable whenNotPaused { bytes32 _offerHash = LibNftRentalMarketplace.hashRentalOffer(_offer); uint64 _expirationDate = uint64(block.timestamp + _duration); LibNftRentalMarketplace.validateAcceptRentalOfferParams( diff --git a/contracts/OriumSftMarketplace.sol b/contracts/OriumSftMarketplace.sol index 2a0dea7..ee5baca 100644 --- a/contracts/OriumSftMarketplace.sol +++ b/contracts/OriumSftMarketplace.sol @@ -5,7 +5,6 @@ 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'; @@ -17,7 +16,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, ReentrancyGuardUpgradeable { +contract OriumSftMarketplace is Initializable, OwnableUpgradeable, PausableUpgradeable { /** ######### Global Variables ########### **/ /// @dev oriumMarketplaceRoyalties stores the collection royalties and fees @@ -117,14 +116,6 @@ contract OriumSftMarketplace is Initializable, OwnableUpgradeable, PausableUpgra transferOwnership(_owner); } - /** - * @notice Initializes the reentrancy guard for the new version. - * @dev This function can only be called once, after the contract upgrade. - */ - function initializeV2() external reinitializer(2) { - __ReentrancyGuard_init(); - } - /** ============================ Rental Functions ================================== **/ /** ######### Setters ########### **/ @@ -179,7 +170,7 @@ contract OriumSftMarketplace is Initializable, OwnableUpgradeable, PausableUpgra function acceptRentalOffer( RentalOffer calldata _offer, uint64 _duration - ) external payable whenNotPaused nonReentrant { + ) external payable whenNotPaused { bytes32 _offerHash = LibOriumSftMarketplace.hashRentalOffer(_offer); uint64 _expirationDate = uint64(block.timestamp + _duration); LibOriumSftMarketplace.validateAcceptRentalOffer( diff --git a/test/NftRentalMarketplace.test.ts b/test/NftRentalMarketplace.test.ts index a8faaf4..c4446d5 100644 --- a/test/NftRentalMarketplace.test.ts +++ b/test/NftRentalMarketplace.test.ts @@ -1049,10 +1049,6 @@ describe('NftRentalMarketplace', () => { 'Initializable: contract is already initialized', ) }) - it('Should NOT initialize the contract with initializeV2 if already initialized', async () => { - await marketplace.initializeV2() - await expect(marketplace.initializeV2()).to.be.revertedWith('Initializable: contract is already initialized') - }) }) describe('Pausable', async () => { describe('Pause', async () => { diff --git a/test/OriumSftMarketplace.test.ts b/test/OriumSftMarketplace.test.ts index 1d28940..8cf7cdc 100644 --- a/test/OriumSftMarketplace.test.ts +++ b/test/OriumSftMarketplace.test.ts @@ -1608,10 +1608,6 @@ describe('OriumSftMarketplace', () => { 'Initializable: contract is already initialized', ) }) - it('Should NOT initialize the contract with initializeV2 if already initialized', async () => { - await marketplace.initializeV2() - await expect(marketplace.initializeV2()).to.be.revertedWith('Initializable: contract is already initialized') - }) }) describe('Pausable', async () => { From 6d0d99ff2748e5467a0b995e97ea0738f0e55af0 Mon Sep 17 00:00:00 2001 From: Eduardo Melo Date: Tue, 13 Aug 2024 16:33:54 -0300 Subject: [PATCH 06/15] feat: including ReentrancyAttack contract to test if the offer can be accept twice in the same tx --- contracts/mocks/ReentrancyAttack.sol | 21 ++++++++++++++++++ test/OriumSftMarketplace.test.ts | 32 ++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 contracts/mocks/ReentrancyAttack.sol diff --git a/contracts/mocks/ReentrancyAttack.sol b/contracts/mocks/ReentrancyAttack.sol new file mode 100644 index 0000000..388dbe2 --- /dev/null +++ b/contracts/mocks/ReentrancyAttack.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.9; + +import '../OriumSftMarketplace.sol'; + +contract ReentrancyAttack { + OriumSftMarketplace public marketplace; + + constructor(OriumSftMarketplace _marketplace) { + marketplace = _marketplace; + } + + receive() external payable {} + + function attemptDoubleAccept(RentalOffer calldata _offer, uint64 _duration) external payable { + // First accept call + marketplace.acceptRentalOffer{ value: msg.value / 2 }(_offer, _duration); + // Second accept call in the same transaction + marketplace.acceptRentalOffer{ value: msg.value / 2 }(_offer, _duration); + } +} diff --git a/test/OriumSftMarketplace.test.ts b/test/OriumSftMarketplace.test.ts index 8cf7cdc..c93b0a5 100644 --- a/test/OriumSftMarketplace.test.ts +++ b/test/OriumSftMarketplace.test.ts @@ -16,6 +16,7 @@ import { OriumSftMarketplace, SftRolesRegistrySingleRole, SftRolesRegistrySingleRoleLegacy, + ReentrancyAttack, } from '../typechain-types' describe('OriumSftMarketplace', () => { @@ -27,6 +28,7 @@ describe('OriumSftMarketplace', () => { let secondMockERC1155: MockERC1155 let wearableToken: MockERC1155 let mockERC20: MockERC20 + let attackContract: ReentrancyAttack // We are disabling this rule because hardhat uses first account as deployer by default, and we are separating deployer and operator // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -721,6 +723,36 @@ describe('OriumSftMarketplace', () => { ).to.be.revertedWith('OriumSftMarketplace: Insufficient native token amount') }) + it('should prevent double accept in the same transaction', async () => { + const AttackContract = await ethers.getContractFactory('ReentrancyAttack') + attackContract = (await AttackContract.deploy(marketplace)) as ReentrancyAttack + await attackContract.waitForDeployment() + + await marketplaceRoyalties + .connect(operator) + .setTrustedFeeTokenForToken([rentalOffer.tokenAddress], [AddressZero], [true]) + + rentalOffer.feeTokenAddress = AddressZero + rentalOffer.feeAmountPerSecond = toWei('0.0000001') + const 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) + + await borrower.sendTransaction({ + to: attackContract.getAddress(), + value: totalFeeAmount, + }) + + const doubleTotalFeeAmount = totalFeeAmount * BigInt(2) + + await expect( + attackContract + .connect(borrower) + .attemptDoubleAccept(rentalOffer, duration, { value: doubleTotalFeeAmount.toString() }), + ).to.be.revertedWith('OriumSftMarketplace: This offer has an ongoing rental') + }) + describe('Fees', async function () { const feeAmountPerSecond = toWei('1') const feeAmount = feeAmountPerSecond * BigInt(duration) From f61365a4f05effd5145a299ac874e8f0950b4e27 Mon Sep 17 00:00:00 2001 From: Eduardo Melo Date: Tue, 13 Aug 2024 20:19:51 -0300 Subject: [PATCH 07/15] fix: changing attack contract --- contracts/mocks/ReentrancyAttack.sol | 30 +++++++++++++++---- test/OriumSftMarketplace.test.ts | 44 +++++++++++++++++++++++----- 2 files changed, 60 insertions(+), 14 deletions(-) diff --git a/contracts/mocks/ReentrancyAttack.sol b/contracts/mocks/ReentrancyAttack.sol index 388dbe2..fbe13ea 100644 --- a/contracts/mocks/ReentrancyAttack.sol +++ b/contracts/mocks/ReentrancyAttack.sol @@ -5,17 +5,35 @@ import '../OriumSftMarketplace.sol'; contract ReentrancyAttack { OriumSftMarketplace public marketplace; + RentalOffer public offer; + uint64 public duration; + bool public reentered = false; constructor(OriumSftMarketplace _marketplace) { marketplace = _marketplace; } - receive() external payable {} + receive() external payable { + if (!reentered && address(marketplace).balance > 0) { + reentered = true; + // Try to re-enter the marketplace + marketplace.acceptRentalOffer{ value: msg.value / 2 }(offer, duration); + } + } + + function attack(RentalOffer calldata _offer, uint64 _duration) external payable { + offer = _offer; + duration = _duration; + + marketplace.acceptRentalOffer{ value: msg.value }(_offer, _duration); + } + + function attackWithRecursiveCalls(RentalOffer calldata _offer, uint64 _duration, uint times) external payable { + offer = _offer; + duration = _duration; - function attemptDoubleAccept(RentalOffer calldata _offer, uint64 _duration) external payable { - // First accept call - marketplace.acceptRentalOffer{ value: msg.value / 2 }(_offer, _duration); - // Second accept call in the same transaction - marketplace.acceptRentalOffer{ value: msg.value / 2 }(_offer, _duration); + for (uint i = 0; i < times; i++) { + marketplace.acceptRentalOffer{ value: msg.value / times }(_offer, _duration); + } } } diff --git a/test/OriumSftMarketplace.test.ts b/test/OriumSftMarketplace.test.ts index c93b0a5..8be9701 100644 --- a/test/OriumSftMarketplace.test.ts +++ b/test/OriumSftMarketplace.test.ts @@ -723,7 +723,38 @@ describe('OriumSftMarketplace', () => { ).to.be.revertedWith('OriumSftMarketplace: Insufficient native token amount') }) - it('should prevent double accept in the same transaction', async () => { + it('should detect reentrancy attack during fee transfer', async () => { + const AttackContract = await ethers.getContractFactory('ReentrancyAttack') + attackContract = (await AttackContract.deploy(marketplace)) as ReentrancyAttack + await attackContract.waitForDeployment() + + await marketplaceRoyalties + .connect(operator) + .setTrustedFeeTokenForToken([rentalOffer.tokenAddress], [AddressZero], [true]) + + rentalOffer.feeTokenAddress = AddressZero + rentalOffer.feeAmountPerSecond = toWei('0.0000001') + const 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) + + // Attempt the attack + try { + await attackContract.attack(rentalOffer, duration, { value: totalFeeAmount }) + console.log('Reentrancy attack did not revert the transaction.') + } catch (error: any) { + if ( + error.message.includes('OriumSftMarketplace: Reentrancy detected or insufficient native token amount') + ) { + console.log('Reentrancy was correctly detected.') + } else { + console.log('The transaction failed for another reason: ', error.message) + } + } + }) + + it('should revert on multiple reentrant calls', async () => { const AttackContract = await ethers.getContractFactory('ReentrancyAttack') attackContract = (await AttackContract.deploy(marketplace)) as ReentrancyAttack await attackContract.waitForDeployment() @@ -741,16 +772,13 @@ describe('OriumSftMarketplace', () => { await borrower.sendTransaction({ to: attackContract.getAddress(), - value: totalFeeAmount, + value: totalFeeAmount * BigInt(6), }) - const doubleTotalFeeAmount = totalFeeAmount * BigInt(2) - + // Attempt the attack await expect( - attackContract - .connect(borrower) - .attemptDoubleAccept(rentalOffer, duration, { value: doubleTotalFeeAmount.toString() }), - ).to.be.revertedWith('OriumSftMarketplace: This offer has an ongoing rental') + attackContract.attackWithRecursiveCalls(rentalOffer, duration, 5, { value: totalFeeAmount }), + ).to.be.revertedWith('OriumSftMarketplace: Insufficient native token amount') }) describe('Fees', async function () { From b602f4d5fda1edd3a84ce4bafb495c8740a5e23f Mon Sep 17 00:00:00 2001 From: Eduardo Melo Date: Wed, 14 Aug 2024 15:30:22 -0300 Subject: [PATCH 08/15] fix: reentrancy test --- contracts/mocks/ReentrancyAttack.sol | 16 +--------------- test/OriumSftMarketplace.test.ts | 28 +++++++++++++--------------- 2 files changed, 14 insertions(+), 30 deletions(-) diff --git a/contracts/mocks/ReentrancyAttack.sol b/contracts/mocks/ReentrancyAttack.sol index fbe13ea..4417f6c 100644 --- a/contracts/mocks/ReentrancyAttack.sol +++ b/contracts/mocks/ReentrancyAttack.sol @@ -7,18 +7,13 @@ contract ReentrancyAttack { OriumSftMarketplace public marketplace; RentalOffer public offer; uint64 public duration; - bool public reentered = false; constructor(OriumSftMarketplace _marketplace) { marketplace = _marketplace; } receive() external payable { - if (!reentered && address(marketplace).balance > 0) { - reentered = true; - // Try to re-enter the marketplace - marketplace.acceptRentalOffer{ value: msg.value / 2 }(offer, duration); - } + marketplace.acceptRentalOffer{ value: msg.value }(offer, duration); } function attack(RentalOffer calldata _offer, uint64 _duration) external payable { @@ -27,13 +22,4 @@ contract ReentrancyAttack { marketplace.acceptRentalOffer{ value: msg.value }(_offer, _duration); } - - function attackWithRecursiveCalls(RentalOffer calldata _offer, uint64 _duration, uint times) external payable { - offer = _offer; - duration = _duration; - - for (uint i = 0; i < times; i++) { - marketplace.acceptRentalOffer{ value: msg.value / times }(_offer, _duration); - } - } } diff --git a/test/OriumSftMarketplace.test.ts b/test/OriumSftMarketplace.test.ts index 8be9701..d35d16b 100644 --- a/test/OriumSftMarketplace.test.ts +++ b/test/OriumSftMarketplace.test.ts @@ -731,27 +731,25 @@ describe('OriumSftMarketplace', () => { await marketplaceRoyalties .connect(operator) .setTrustedFeeTokenForToken([rentalOffer.tokenAddress], [AddressZero], [true]) - + rentalOffer.minDuration = duration rentalOffer.feeTokenAddress = AddressZero rentalOffer.feeAmountPerSecond = toWei('0.0000001') const 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) - // Attempt the attack - try { - await attackContract.attack(rentalOffer, duration, { value: totalFeeAmount }) - console.log('Reentrancy attack did not revert the transaction.') - } catch (error: any) { - if ( - error.message.includes('OriumSftMarketplace: Reentrancy detected or insufficient native token amount') - ) { - console.log('Reentrancy was correctly detected.') - } else { - console.log('The transaction failed for another reason: ', error.message) - } - } + await attackContract.connect(lender).attack(rentalOffer, duration, { + value: totalFeeAmount, + }) + + await expect( + borrower.sendTransaction({ + to: attackContract.getAddress(), + value: toWei('1'), + }), + ).to.be.revertedWith('OriumSftMarketplace: This offer has an ongoing rental') }) it('should revert on multiple reentrant calls', async () => { @@ -772,7 +770,7 @@ describe('OriumSftMarketplace', () => { await borrower.sendTransaction({ to: attackContract.getAddress(), - value: totalFeeAmount * BigInt(6), + value: toWei('100'), }) // Attempt the attack From 51509d02ccef7150e9bec65d8afa5ee3b0d46982 Mon Sep 17 00:00:00 2001 From: Eduardo Melo Date: Wed, 14 Aug 2024 15:40:50 -0300 Subject: [PATCH 09/15] fix: delete recursive --- test/OriumSftMarketplace.test.ts | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/test/OriumSftMarketplace.test.ts b/test/OriumSftMarketplace.test.ts index d35d16b..67ecbd2 100644 --- a/test/OriumSftMarketplace.test.ts +++ b/test/OriumSftMarketplace.test.ts @@ -752,33 +752,6 @@ describe('OriumSftMarketplace', () => { ).to.be.revertedWith('OriumSftMarketplace: This offer has an ongoing rental') }) - it('should revert on multiple reentrant calls', async () => { - const AttackContract = await ethers.getContractFactory('ReentrancyAttack') - attackContract = (await AttackContract.deploy(marketplace)) as ReentrancyAttack - await attackContract.waitForDeployment() - - await marketplaceRoyalties - .connect(operator) - .setTrustedFeeTokenForToken([rentalOffer.tokenAddress], [AddressZero], [true]) - - rentalOffer.feeTokenAddress = AddressZero - rentalOffer.feeAmountPerSecond = toWei('0.0000001') - const 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) - - await borrower.sendTransaction({ - to: attackContract.getAddress(), - value: toWei('100'), - }) - - // Attempt the attack - await expect( - attackContract.attackWithRecursiveCalls(rentalOffer, duration, 5, { value: totalFeeAmount }), - ).to.be.revertedWith('OriumSftMarketplace: Insufficient native token amount') - }) - describe('Fees', async function () { const feeAmountPerSecond = toWei('1') const feeAmount = feeAmountPerSecond * BigInt(duration) From c2c882e796ba96c17199226be5de3c94c30557cf Mon Sep 17 00:00:00 2001 From: Eduardo Melo Date: Wed, 14 Aug 2024 16:46:16 -0300 Subject: [PATCH 10/15] fix: reentrancy --- test/OriumSftMarketplace.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/OriumSftMarketplace.test.ts b/test/OriumSftMarketplace.test.ts index 67ecbd2..788f549 100644 --- a/test/OriumSftMarketplace.test.ts +++ b/test/OriumSftMarketplace.test.ts @@ -723,7 +723,7 @@ describe('OriumSftMarketplace', () => { ).to.be.revertedWith('OriumSftMarketplace: Insufficient native token amount') }) - it('should detect reentrancy attack during fee transfer', async () => { + it.only('should detect reentrancy attack during fee transfer', async () => { const AttackContract = await ethers.getContractFactory('ReentrancyAttack') attackContract = (await AttackContract.deploy(marketplace)) as ReentrancyAttack await attackContract.waitForDeployment() @@ -745,7 +745,7 @@ describe('OriumSftMarketplace', () => { }) await expect( - borrower.sendTransaction({ + lender.sendTransaction({ to: attackContract.getAddress(), value: toWei('1'), }), From 6ed60f29d2296e6747e0af4d00f132ebd4cfd5e7 Mon Sep 17 00:00:00 2001 From: Eduardo Melo Date: Wed, 14 Aug 2024 17:12:56 -0300 Subject: [PATCH 11/15] fix: reentrancy only --- test/OriumSftMarketplace.test.ts | 37 +++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/test/OriumSftMarketplace.test.ts b/test/OriumSftMarketplace.test.ts index 788f549..e45e4b2 100644 --- a/test/OriumSftMarketplace.test.ts +++ b/test/OriumSftMarketplace.test.ts @@ -723,7 +723,7 @@ describe('OriumSftMarketplace', () => { ).to.be.revertedWith('OriumSftMarketplace: Insufficient native token amount') }) - it.only('should detect reentrancy attack during fee transfer', async () => { + it('should detect reentrancy attack during fee transfer', async () => { const AttackContract = await ethers.getContractFactory('ReentrancyAttack') attackContract = (await AttackContract.deploy(marketplace)) as ReentrancyAttack await attackContract.waitForDeployment() @@ -752,6 +752,41 @@ describe('OriumSftMarketplace', () => { ).to.be.revertedWith('OriumSftMarketplace: This offer has an ongoing rental') }) + it('should revert when accept offer is called from Attack contract after a offer be accepeted by borrower', async () => { + const AttackContract = await ethers.getContractFactory('ReentrancyAttack') + attackContract = (await AttackContract.deploy(marketplace)) as ReentrancyAttack + await attackContract.waitForDeployment() + + await marketplaceRoyalties + .connect(operator) + .setTrustedFeeTokenForToken([rentalOffer.tokenAddress], [AddressZero], [true]) + rentalOffer.minDuration = duration + rentalOffer.feeTokenAddress = AddressZero + rentalOffer.feeAmountPerSecond = toWei('0.0000001') + const 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) + + 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) + + await expect( + attackContract.connect(lender).attack(rentalOffer, duration, { + value: totalFeeAmount, + }), + ).to.be.revertedWith('OriumSftMarketplace: This offer has an ongoing rental') + }) + describe('Fees', async function () { const feeAmountPerSecond = toWei('1') const feeAmount = feeAmountPerSecond * BigInt(duration) From 8afd1d3ed4d93f4e50881192cb765b4f43466476 Mon Sep 17 00:00:00 2001 From: Eduardo Melo Date: Thu, 15 Aug 2024 16:12:02 -0300 Subject: [PATCH 12/15] feat: release version with Check Effect Interact to transferFee --- contracts/NftRentalMarketplace.sol | 3 +- contracts/OriumSftMarketplace.sol | 3 +- test/OriumSftMarketplace.test.ts | 66 +----------------------------- 3 files changed, 3 insertions(+), 69 deletions(-) diff --git a/contracts/NftRentalMarketplace.sol b/contracts/NftRentalMarketplace.sol index 378bcf8..62ebfa3 100644 --- a/contracts/NftRentalMarketplace.sol +++ b/contracts/NftRentalMarketplace.sol @@ -162,8 +162,6 @@ contract NftRentalMarketplace is Initializable, OwnableUpgradeable, PausableUpgr _expirationDate ); - _transferFees(_offer.tokenAddress, _offer.feeTokenAddress, _offer.feeAmountPerSecond, _duration, _offer.lender); - LibNftRentalMarketplace.grantRoles( oriumMarketplaceRoyalties, _offer.tokenAddress, @@ -181,6 +179,7 @@ contract NftRentalMarketplace is Initializable, OwnableUpgradeable, PausableUpgr } rentals[_offerHash] = Rental({ borrower: msg.sender, expirationDate: _expirationDate }); + _transferFees(_offer.tokenAddress, _offer.feeTokenAddress, _offer.feeAmountPerSecond, _duration, _offer.lender); emit RentalStarted(_offer.lender, _offer.nonce, msg.sender, _expirationDate); } diff --git a/contracts/OriumSftMarketplace.sol b/contracts/OriumSftMarketplace.sol index ee5baca..0ddf6b7 100644 --- a/contracts/OriumSftMarketplace.sol +++ b/contracts/OriumSftMarketplace.sol @@ -183,8 +183,6 @@ contract OriumSftMarketplace is Initializable, OwnableUpgradeable, PausableUpgra _expirationDate ); - _transferFees(_offer.tokenAddress, _offer.feeTokenAddress, _offer.feeAmountPerSecond, _duration, _offer.lender); - IERC7589 _rolesRegistry = IERC7589( IOriumMarketplaceRoyalties(oriumMarketplaceRoyalties).sftRolesRegistryOf(_offer.tokenAddress) ); @@ -200,6 +198,7 @@ contract OriumSftMarketplace is Initializable, OwnableUpgradeable, PausableUpgra } rentals[_offerHash] = Rental({ borrower: msg.sender, expirationDate: _expirationDate }); + _transferFees(_offer.tokenAddress, _offer.feeTokenAddress, _offer.feeAmountPerSecond, _duration, _offer.lender); emit RentalStarted(_offer.lender, _offer.nonce, msg.sender, _expirationDate); } diff --git a/test/OriumSftMarketplace.test.ts b/test/OriumSftMarketplace.test.ts index e45e4b2..ac6a9df 100644 --- a/test/OriumSftMarketplace.test.ts +++ b/test/OriumSftMarketplace.test.ts @@ -703,7 +703,7 @@ describe('OriumSftMarketplace', () => { .withArgs(rentalOffer.lender, rentalOffer.nonce, borrower.address, expirationDate) }) - it('Should revert when accepting a rental offer with insufficient native tokens', async function () { + it('Should revert when accept a rental offer with insufficient native tokens', async function () { await marketplaceRoyalties .connect(operator) .setTrustedFeeTokenForToken([rentalOffer.tokenAddress], [AddressZero], [true]) @@ -723,70 +723,6 @@ describe('OriumSftMarketplace', () => { ).to.be.revertedWith('OriumSftMarketplace: Insufficient native token amount') }) - it('should detect reentrancy attack during fee transfer', async () => { - const AttackContract = await ethers.getContractFactory('ReentrancyAttack') - attackContract = (await AttackContract.deploy(marketplace)) as ReentrancyAttack - await attackContract.waitForDeployment() - - await marketplaceRoyalties - .connect(operator) - .setTrustedFeeTokenForToken([rentalOffer.tokenAddress], [AddressZero], [true]) - rentalOffer.minDuration = duration - rentalOffer.feeTokenAddress = AddressZero - rentalOffer.feeAmountPerSecond = toWei('0.0000001') - const 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) - - await attackContract.connect(lender).attack(rentalOffer, duration, { - value: totalFeeAmount, - }) - - await expect( - lender.sendTransaction({ - to: attackContract.getAddress(), - value: toWei('1'), - }), - ).to.be.revertedWith('OriumSftMarketplace: This offer has an ongoing rental') - }) - - it('should revert when accept offer is called from Attack contract after a offer be accepeted by borrower', async () => { - const AttackContract = await ethers.getContractFactory('ReentrancyAttack') - attackContract = (await AttackContract.deploy(marketplace)) as ReentrancyAttack - await attackContract.waitForDeployment() - - await marketplaceRoyalties - .connect(operator) - .setTrustedFeeTokenForToken([rentalOffer.tokenAddress], [AddressZero], [true]) - rentalOffer.minDuration = duration - rentalOffer.feeTokenAddress = AddressZero - rentalOffer.feeAmountPerSecond = toWei('0.0000001') - const 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) - - 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) - - await expect( - attackContract.connect(lender).attack(rentalOffer, duration, { - value: totalFeeAmount, - }), - ).to.be.revertedWith('OriumSftMarketplace: This offer has an ongoing rental') - }) - describe('Fees', async function () { const feeAmountPerSecond = toWei('1') const feeAmount = feeAmountPerSecond * BigInt(duration) From 77f2ddf4c8e66808cb4489f974e08d1d0d2665a9 Mon Sep 17 00:00:00 2001 From: Eduardo Melo Date: Fri, 16 Aug 2024 01:39:22 -0300 Subject: [PATCH 13/15] feat: networks files sft marketplace --- .openzeppelin/polygon.json | 183 +++++++++++++++++++++++++++++++++++ addresses/polygon/index.json | 4 +- 2 files changed, 185 insertions(+), 2 deletions(-) diff --git a/.openzeppelin/polygon.json b/.openzeppelin/polygon.json index 0a35727..6a0989e 100644 --- a/.openzeppelin/polygon.json +++ b/.openzeppelin/polygon.json @@ -4030,6 +4030,189 @@ }, "namespaces": {} } + }, + "27b07b4cb603b26fff3ef1cbbfd39da1d0266a774fac0884ae1c2ed6caacbf7a": { + "address": "0x8aFfF4377E66782287dd08e6C793eaD674D9685D", + "txHash": "0x56200589431d4b902765fbdaefe35aac263281e2d7a543d1f22cf86db5f27487", + "layout": { + "solcVersion": "0.8.9", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:40" + }, + { + "label": "_owner", + "offset": 0, + "slot": "51", + "type": "t_address", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" + }, + { + "label": "__gap", + "offset": 0, + "slot": "52", + "type": "t_array(t_uint256)49_storage", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94" + }, + { + "label": "_paused", + "offset": 0, + "slot": "101", + "type": "t_bool", + "contract": "PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:29" + }, + { + "label": "__gap", + "offset": 0, + "slot": "102", + "type": "t_array(t_uint256)49_storage", + "contract": "PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:116" + }, + { + "label": "oriumMarketplaceRoyalties", + "offset": 0, + "slot": "151", + "type": "t_address", + "contract": "OriumSftMarketplace", + "src": "contracts/OriumSftMarketplace.sol:23" + }, + { + "label": "isCreated", + "offset": 0, + "slot": "152", + "type": "t_mapping(t_bytes32,t_bool)", + "contract": "OriumSftMarketplace", + "src": "contracts/OriumSftMarketplace.sol:29" + }, + { + "label": "nonceDeadline", + "offset": 0, + "slot": "153", + "type": "t_mapping(t_address,t_mapping(t_uint256,t_uint64))", + "contract": "OriumSftMarketplace", + "src": "contracts/OriumSftMarketplace.sol:32" + }, + { + "label": "commitmentIdToNonce", + "offset": 0, + "slot": "154", + "type": "t_mapping(t_address,t_mapping(t_uint256,t_uint256))", + "contract": "OriumSftMarketplace", + "src": "contracts/OriumSftMarketplace.sol:35" + }, + { + "label": "rentals", + "offset": 0, + "slot": "155", + "type": "t_mapping(t_bytes32,t_struct(Rental)7265_storage)", + "contract": "OriumSftMarketplace", + "src": "contracts/OriumSftMarketplace.sol:38" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_mapping(t_uint256,t_uint256))": { + "label": "mapping(address => mapping(uint256 => uint256))", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_mapping(t_uint256,t_uint64))": { + "label": "mapping(address => mapping(uint256 => uint64))", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_bool)": { + "label": "mapping(bytes32 => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(Rental)7265_storage)": { + "label": "mapping(bytes32 => struct OriumSftMarketplace.Rental)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_uint256)": { + "label": "mapping(uint256 => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_uint64)": { + "label": "mapping(uint256 => uint64)", + "numberOfBytes": "32" + }, + "t_struct(Rental)7265_storage": { + "label": "struct OriumSftMarketplace.Rental", + "members": [ + { + "label": "borrower", + "type": "t_address", + "offset": 0, + "slot": "0" + }, + { + "label": "expirationDate", + "type": "t_uint64", + "offset": 20, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint64": { + "label": "uint64", + "numberOfBytes": "8" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + }, + "namespaces": {} + } } } } diff --git a/addresses/polygon/index.json b/addresses/polygon/index.json index d53134d..aa87c0b 100644 --- a/addresses/polygon/index.json +++ b/addresses/polygon/index.json @@ -29,10 +29,10 @@ "OriumSftMarketplace": { "address": "0xB1D47B09aa6D81d7B00C3A37705a6A157B83C49F", "operator": "0x359E1208DE02Af11461A37D72165Ef2dcD2Adfc8", - "implementation": "0x24249E232b49740e940d1CE04Ce5B0b50e8dAeF8", + "implementation": "0x8aFfF4377E66782287dd08e6C793eaD674D9685D", "proxyAdmin": "0x48c769f6a8de57d824f0e7330d7A27dee53a43cD", "libraries": { - "LibOriumSftMarketplace": "0x7D037fCfD9147c2182b50ec4FeEf0231F91DaA3d" + "LibOriumSftMarketplace": "0x71C24d086FF041C86CEA2737fd70cA5F1a400f23" } }, "ERC7432WrapperForERC4907": { From c41a07c68bdefcd2198573f8965076b12ffbedbc Mon Sep 17 00:00:00 2001 From: Eduardo Melo Date: Fri, 16 Aug 2024 10:14:35 -0300 Subject: [PATCH 14/15] fix:fixing require native token nft marketplace to == --- contracts/NftRentalMarketplace.sol | 2 +- test/NftRentalMarketplace.test.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/NftRentalMarketplace.sol b/contracts/NftRentalMarketplace.sol index 62ebfa3..f2f07ff 100644 --- a/contracts/NftRentalMarketplace.sol +++ b/contracts/NftRentalMarketplace.sol @@ -314,7 +314,7 @@ contract NftRentalMarketplace is Initializable, OwnableUpgradeable, PausableUpgr ) internal { if (_feeTokenAddress == address(0)) { uint256 totalFeeAmount = _feeAmountPerSecond * _duration; - require(msg.value >= totalFeeAmount, 'NftRentalMarketplace: Incorrect native token amount'); + require(msg.value == totalFeeAmount, 'NftRentalMarketplace: Incorrect native token amount'); uint256 marketplaceFeeAmount = LibNftRentalMarketplace.getAmountFromPercentage( totalFeeAmount, diff --git a/test/NftRentalMarketplace.test.ts b/test/NftRentalMarketplace.test.ts index c4446d5..166e339 100644 --- a/test/NftRentalMarketplace.test.ts +++ b/test/NftRentalMarketplace.test.ts @@ -484,13 +484,14 @@ describe('NftRentalMarketplace', () => { .connect(operator) .setTrustedFeeTokenForToken([rentalOffer.tokenAddress], [AddressZero], [true]) await marketplace.connect(lender).createRentalOffer(rentalOffer) + const totalFeeAmount = rentalOffer.feeAmountPerSecond * BigInt(duration) const blockTimestamp = await time.latest() const expirationDate = blockTimestamp + duration + 1 await expect( marketplace.connect(borrower).acceptRentalOffer(rentalOffer, duration, { - value: totalFeeAmount.toString(), + value: totalFeeAmount, }), ) .to.emit(marketplace, 'RentalStarted') From 13daa7c42143bd011368ca7ec058998258971bab Mon Sep 17 00:00:00 2001 From: Eduardo Melo Date: Fri, 16 Aug 2024 11:24:18 -0300 Subject: [PATCH 15/15] feat: networks files 721 moonbeam --- .openzeppelin/unknown-1284.json | 179 ++++++++++++++++++++++++++++++++ addresses/moonbeam/index.json | 4 +- 2 files changed, 181 insertions(+), 2 deletions(-) diff --git a/.openzeppelin/unknown-1284.json b/.openzeppelin/unknown-1284.json index 2311d06..028ad05 100644 --- a/.openzeppelin/unknown-1284.json +++ b/.openzeppelin/unknown-1284.json @@ -1111,6 +1111,185 @@ }, "namespaces": {} } + }, + "d30893d8e86922c92e4b3e9681cb8395730160aa4da35e933bb6308357c89532": { + "address": "0x558eb7a37CAEE6d6F151312dA00FADb8d25937AE", + "txHash": "0xd076bb6cc3366cdbf01100409036519266e1589f66f0851fe83cf7066e88fdf3", + "layout": { + "solcVersion": "0.8.9", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:40" + }, + { + "label": "_owner", + "offset": 0, + "slot": "51", + "type": "t_address", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" + }, + { + "label": "__gap", + "offset": 0, + "slot": "52", + "type": "t_array(t_uint256)49_storage", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94" + }, + { + "label": "_paused", + "offset": 0, + "slot": "101", + "type": "t_bool", + "contract": "PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:29" + }, + { + "label": "__gap", + "offset": 0, + "slot": "102", + "type": "t_array(t_uint256)49_storage", + "contract": "PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:116" + }, + { + "label": "oriumMarketplaceRoyalties", + "offset": 0, + "slot": "151", + "type": "t_address", + "contract": "NftRentalMarketplace", + "src": "contracts/NftRentalMarketplace.sol:23" + }, + { + "label": "isCreated", + "offset": 0, + "slot": "152", + "type": "t_mapping(t_bytes32,t_bool)", + "contract": "NftRentalMarketplace", + "src": "contracts/NftRentalMarketplace.sol:26" + }, + { + "label": "nonceDeadline", + "offset": 0, + "slot": "153", + "type": "t_mapping(t_address,t_mapping(t_uint256,t_uint64))", + "contract": "NftRentalMarketplace", + "src": "contracts/NftRentalMarketplace.sol:29" + }, + { + "label": "roleDeadline", + "offset": 0, + "slot": "154", + "type": "t_mapping(t_bytes32,t_mapping(t_address,t_mapping(t_uint256,t_uint64)))", + "contract": "NftRentalMarketplace", + "src": "contracts/NftRentalMarketplace.sol:32" + }, + { + "label": "rentals", + "offset": 0, + "slot": "155", + "type": "t_mapping(t_bytes32,t_struct(Rental)9028_storage)", + "contract": "NftRentalMarketplace", + "src": "contracts/NftRentalMarketplace.sol:35" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_mapping(t_uint256,t_uint64))": { + "label": "mapping(address => mapping(uint256 => uint64))", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_bool)": { + "label": "mapping(bytes32 => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_mapping(t_address,t_mapping(t_uint256,t_uint64)))": { + "label": "mapping(bytes32 => mapping(address => mapping(uint256 => uint64)))", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(Rental)9028_storage)": { + "label": "mapping(bytes32 => struct Rental)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_uint64)": { + "label": "mapping(uint256 => uint64)", + "numberOfBytes": "32" + }, + "t_struct(Rental)9028_storage": { + "label": "struct Rental", + "members": [ + { + "label": "borrower", + "type": "t_address", + "offset": 0, + "slot": "0" + }, + { + "label": "expirationDate", + "type": "t_uint64", + "offset": 20, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint64": { + "label": "uint64", + "numberOfBytes": "8" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + }, + "namespaces": {} + } } } } diff --git a/addresses/moonbeam/index.json b/addresses/moonbeam/index.json index 8625b84..abdcf05 100644 --- a/addresses/moonbeam/index.json +++ b/addresses/moonbeam/index.json @@ -35,10 +35,10 @@ "NftRentalMarketplace": { "address": "0x201E1636BB21Dfd51F93815BCD008EAe2Fa29bD9", "operator": "0x04c8c6c56dab836f8bd62cb6884371507e706806", - "implementation": "0x830cbc0e100e72e8682391a668ce6c9db703848f", + "implementation": "0x558eb7a37CAEE6d6F151312dA00FADb8d25937AE", "proxyAdmin": "0x668e73cF24361cfE13801681d8885e6632A7Eaa6", "libraries": { - "LibNftRentalMarketplace": "0x4BDb2d8f3833b38307dfC19231dC7cD65b59504a" + "LibNftRentalMarketplace": "0x89aD0a3E1F72eb14281Ec02Fdf5dcf4FB5e01845" } } } \ No newline at end of file