Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

ON-468: Create Offers off-chain #11

Merged
merged 12 commits into from
Sep 25, 2023
Merged
205 changes: 196 additions & 9 deletions contracts/OriumMarketplace.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,28 @@
pragma solidity 0.8.9;

import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import { IERC1155 } from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";

import { EIP712Upgradeable } from "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol";
ernanirst marked this conversation as resolved.
Show resolved Hide resolved
import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
/**
* @title Orium Marketplace - Marketplace for renting NFTs
* @dev This contract is used to manage NFTs rentals, powered by ERC-7432 Non-Fungible Token Roles
* @author Orium Network Team - [email protected]
*/
contract OriumMarketplace is Initializable, OwnableUpgradeable, PausableUpgradeable {
contract OriumMarketplace is Initializable, OwnableUpgradeable, PausableUpgradeable, EIP712Upgradeable {
/** ######### Constants ########### **/

/// @dev 100 ether is 100%
uint256 public constant MAX_PERCENTAGE = 100 ether;
/// @dev 2.5 ether is 2.5%
uint256 public constant DEFAULT_FEE_PERCENTAGE = 2.5 ether;

/** ######### Global Variables ########### **/

/// @dev rolesRegistry is a ERC-7432 contract
address public rolesRegistry;
/// @dev deadline is set in seconds
Expand All @@ -29,6 +36,14 @@ contract OriumMarketplace is Initializable, OwnableUpgradeable, PausableUpgradea
/// @dev tokenAddress => royaltyInfo
mapping(address => RoyaltyInfo) public royaltyInfo;

/// @dev nonce => hashedOffer => isPresigned
mapping(uint256 => mapping(bytes32 => bool)) public preSignedOffer;

/// @dev lender => nonce => bool
mapping(address => mapping(uint256 => uint256)) public nonceDeadline;

/** ######### Structs ########### **/

/// @dev Royalty info. Used to charge fees for the creator.
struct RoyaltyInfo {
address creator;
Expand All @@ -42,14 +57,91 @@ contract OriumMarketplace is Initializable, OwnableUpgradeable, PausableUpgradea
bool isCustomFee;
}

/// @dev Rental offer info.
struct RentalOffer {
address lender;
address borrower;
address tokenAddress;
uint256 tokenId;
address feeTokenAddress;
uint256 feeAmountPerSecond;
uint256 nonce;
uint64 deadline;
bytes32[] roles;
bytes[] rolesData;
}

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

/**
* @param tokenAddress The NFT address.
* @param feePercentageInWei The fee percentage in wei.
* @param isCustomFee If the fee is custom or not. Used to allow collections with no fee.
*/
event MarketplaceFeeSet(address indexed tokenAddress, uint256 feePercentageInWei, bool isCustomFee);
/**
* @param tokenAddress The NFT address.
* @param creator The address of the creator.
* @param royaltyPercentageInWei The royalty percentage in wei.
* @param treasury The address where the fees will be sent. If the treasury is address(0), the fees will be burned.
*/
event CreatorRoyaltySet(
address indexed tokenAddress,
address indexed creator,
karacurt marked this conversation as resolved.
Show resolved Hide resolved
uint256 royaltyPercentageInWei,
address treasury
);
/**
* @param nonce The nonce of the rental offer
* @param lender The address of the user lending the NFT
* @param borrower The address of the user renting the NFT
* @param tokenAddress The address of the contract of the NFT to rent
* @param tokenId The tokenId of the NFT to rent
* @param feeTokenAddress The address of the ERC20 token for rental fees
* @param feeAmountPerSecond The amount of fee per second
* @param deadline The deadline until when the rental offer is valid
* @param roles The array of roles to be assigned to the borrower
* @param rolesData The array of data for each role
*/
event RentalOfferCreated(
uint256 indexed nonce,
address indexed lender,
address borrower,
address tokenAddress,
uint256 tokenId,
address feeTokenAddress,
uint256 feeAmountPerSecond,
uint256 deadline,
bytes32[] roles,
bytes[] rolesData
);

/** ######### Modifiers ########### **/

/**
* @notice Checks the ownership of the token.
* @dev Throws if the caller is not the owner of the token.
* @param _tokenAddress The NFT address.
* @param _tokenId The id of the token.
*/
modifier onlyTokenOwner(address _tokenAddress, uint256 _tokenId) {
if (isERC1155(_tokenAddress)) {
require(
IERC1155(_tokenAddress).balanceOf(msg.sender, _tokenId) > 0,
"OriumMarketplace: only token owner can call this function"
);
} else if(isERC721(_tokenAddress)) {
require(
msg.sender == IERC721(_tokenAddress).ownerOf(_tokenId),
"OriumMarketplace: only token owner can call this function"
);
} else {
revert("OriumMarketplace: token address is not ERC1155 or ERC721");
}
_;
}

/** ######### Initializer ########### **/
/**
* @notice Initializes the contract.
* @dev The owner of the contract will be the owner of the protocol.
Expand All @@ -67,10 +159,86 @@ contract OriumMarketplace is Initializable, OwnableUpgradeable, PausableUpgradea
transferOwnership(_owner);
}

function marketplaceFeeOf(address _tokenAddress) public view returns (uint256) {
return feeInfo[_tokenAddress].isCustomFee ? feeInfo[_tokenAddress].feePercentageInWei : DEFAULT_FEE_PERCENTAGE;
/** ============================ Rental Functions ================================== **/

/** ######### Setters ########### **/
/**
* @notice Creates a rental offer.
* @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 onlyTokenOwner(_offer.tokenAddress, _offer.tokenId) {
require(msg.sender == _offer.lender, "OriumMarketplace: Sender and Lender mismatch");
require(
_offer.roles.length == _offer.rolesData.length,
"OriumMarketplace: roles and rolesData should have the same length"
);
require(
_offer.deadline <= block.timestamp + maxDeadline && _offer.deadline > block.timestamp,
"OriumMarketplace: Invalid deadline"
);
require(nonceDeadline[_offer.lender][_offer.nonce] == 0, "OriumMarketplace: Nonce already used");

nonceDeadline[_offer.lender][_offer.nonce] = _offer.deadline;
preSignedOffer[_offer.nonce][hashRentalOffer(_offer)] = true;

emit RentalOfferCreated(
_offer.nonce,
_offer.lender,
_offer.borrower,
_offer.tokenAddress,
_offer.tokenId,
_offer.feeTokenAddress,
_offer.feeAmountPerSecond,
_offer.deadline,
_offer.roles,
_offer.rolesData
);
}

/** ######### Getters ########### **/

/**
* @notice Gets the rental offer hash.
* @param _offer The rental offer struct to be hashed.
*/
function hashRentalOffer(RentalOffer memory _offer) public view returns (bytes32) {
return
_hashTypedDataV4(
keccak256(
abi.encode(
keccak256(
"RentalOffer(address lender,address borrower,address tokenAddress,uint256 tokenId,address feeTokenAddress,uint256 feeAmountPerSecond,uint256 nonce,uint64 deadline,bytes32[] roles,bytes[] rolesData)"
),
_offer.lender,
_offer.borrower,
_offer.tokenAddress,
_offer.tokenId,
_offer.feeTokenAddress,
_offer.feeAmountPerSecond,
_offer.nonce,
_offer.deadline,
_offer.roles,
_offer.rolesData
)
)
);
}

function isERC1155(address _tokenAddress) public view returns (bool) {
return ERC165Checker.supportsInterface(_tokenAddress, type(IERC1155).interfaceId);
}

function isERC721(address _tokenAddress) public view returns (bool) {
return ERC165Checker.supportsInterface(_tokenAddress, type(IERC721).interfaceId);
}

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

/** ######### Setters ########### **/

/**
* @notice Sets the roles registry.
* @dev Only owner can set the roles registry.
Expand All @@ -90,7 +258,7 @@ contract OriumMarketplace is Initializable, OwnableUpgradeable, PausableUpgradea
/**
* @notice Sets the marketplace fee for a collection.
* @dev If no fee is set, the default fee will be used.
* @param _tokenAddress The address of the collection.
* @param _tokenAddress The NFT address.
* @param _feePercentageInWei The fee percentage in wei.
* @param _isCustomFee If the fee is custom or not.
*/
Expand All @@ -113,17 +281,17 @@ contract OriumMarketplace is Initializable, OwnableUpgradeable, PausableUpgradea
/**
* @notice Sets the royalty info.
* @dev Only owner can associate a collection with a creator.
* @param _tokenAddress The address of the collection.
cd * @param _creator The address of the creator.
* @param _tokenAddress The NFT address.
* @param _creator The address of the creator.
*/
function setCreator(address _tokenAddress, address _creator) external onlyOwner {
_setRoyalty(_creator, _tokenAddress, 0, address(0));
}

/**
* @notice Sets the royalty info.
* @param _tokenAddress The address of the collection.
* @param _royaltyPercentageInWei The royalty percentage in wei. If the fee is 0, the creator fee will be disabled.
* @param _tokenAddress The NFT address.
* @param _royaltyPercentageInWei The royalty percentage in wei.
* @param _treasury The address where the fees will be sent. If the treasury is address(0), the fees will be burned.
*/
function setRoyaltyInfo(address _tokenAddress, uint256 _royaltyPercentageInWei, address _treasury) external {
Expand All @@ -135,6 +303,14 @@ cd * @param _creator The address of the creator.
_setRoyalty(msg.sender, _tokenAddress, _royaltyPercentageInWei, _treasury);
}

/**
* @notice Sets the royalty info.
* @dev Only owner can associate a collection with a creator.
* @param _creator The address of the creator.
* @param _tokenAddress The NFT address.
* @param _royaltyPercentageInWei The royalty percentage in wei.
* @param _treasury The address where the fees will be sent. If the treasury is address(0), the fees will be burned.
*/
function _setRoyalty(
address _creator,
address _tokenAddress,
Expand Down Expand Up @@ -164,4 +340,15 @@ cd * @param _creator The address of the creator.
require(_maxDeadline > 0, "OriumMarketplace: Max deadline should be greater than 0");
maxDeadline = _maxDeadline;
}

/** ######### Getters ########### **/

/**
* @notice Gets the marketplace fee for a collection.
* @dev If no custom fee is set, the default fee will be used.
* @param _tokenAddress The NFT address.
*/
function marketplaceFeeOf(address _tokenAddress) public view returns (uint256) {
return feeInfo[_tokenAddress].isCustomFee ? feeInfo[_tokenAddress].feePercentageInWei : DEFAULT_FEE_PERCENTAGE;
}
}
22 changes: 22 additions & 0 deletions contracts/mocks/MockERC1155.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: CC0-1.0

pragma solidity 0.8.9;

import { ERC1155 } from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";

/**
* @title MockERC1155
* @dev Mock contract for testing purposes.
*/

contract MockERC1155 is ERC1155 {
constructor() ERC1155("") {}

function mint(address to, uint256 tokenId, uint256 amount, bytes memory data) external {
_mint(to, tokenId, amount, data);
}

function burn(address account, uint256 tokenId, uint256 amount) external {
_burn(account, tokenId, amount);
}
}
18 changes: 18 additions & 0 deletions contracts/mocks/MockERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: CC0-1.0

pragma solidity 0.8.9;

import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

/**
* @title MockERC20
* @dev Mock contract for testing purposes.
*/

contract MockERC20 is ERC20 {
constructor() ERC20("PaymentToken", "PAY") {}

function mint(address to, uint256 amount) external {
_mint(to, amount);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ pragma solidity 0.8.9;
import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";

/**
* @title MockNft
* @title MockERC721
* @dev Mock contract for testing purposes.
*/

contract MockNft is ERC721 {
contract MockERC721 is ERC721 {
constructor() ERC721("MockNft", "MOCK") {}

function mint(address to, uint256 tokenId) external {
Expand Down
Loading