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

feat: events and acl #1

Merged
merged 4 commits into from
Aug 10, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: emit events
CanvasL committed Aug 6, 2024
commit 88086b1b096d7dfc787f7ebf14e711b19ea447bb
File renamed without changes.
File renamed without changes.
128 changes: 109 additions & 19 deletions src/Marketplace.sol → contracts/Marketplace.sol
Original file line number Diff line number Diff line change
@@ -53,22 +53,44 @@ contract Marketplace is IMarketplace, Ownable {
_feePoints = feePoints;
}

function getListingInfo(address accessToken, uint256 tokenId) external view returns (ListingInfo memory) {
/**
* @inheritdoc IMarketplace
*/
function getListingInfo(
address accessToken,
uint256 tokenId
) external view returns (ListingInfo memory) {
return _listings[accessToken][tokenId];
}

function getRentalInfo(address accessToken, uint256 tokenId, address tenant) external view returns (RentalInfo memory) {
/**
* @inheritdoc IMarketplace
*/
function getRentalInfo(
address accessToken,
uint256 tokenId,
address tenant
) external view returns (RentalInfo memory) {
return _rentals[accessToken][tokenId][tenant];
}

/**
* @inheritdoc IMarketplace
*/
function setFeePoints(uint256 feePoints) external onlyOwner {
_feePoints = feePoints;
}

/**
* @inheritdoc IMarketplace
*/
function setTreasury(address payable treasury) external onlyOwner {
_treasury = treasury;
}

/**
* @inheritdoc IMarketplace
*/
function addRentCurrencies(
address[] memory rentCurrencies
) external onlyOwner {
@@ -77,6 +99,9 @@ contract Marketplace is IMarketplace, Ownable {
}
}

/**
* @inheritdoc IMarketplace
*/
function removeRentCurrencies(
address[] memory rentCurrencies
) external onlyOwner {
@@ -85,11 +110,12 @@ contract Marketplace is IMarketplace, Ownable {
}
}

/**
* @inheritdoc IMarketplace
*/
function list(ListArgs memory args) public {
address accessToken = ACCESS_TOKEN_FACTORY.getAccessToken(
args.product
);
if(accessToken == address(0)) {
address accessToken = ACCESS_TOKEN_FACTORY.getAccessToken(args.product);
if (accessToken == address(0)) {
accessToken = ACCESS_TOKEN_FACTORY.createAccessToken(args.product);
}
require(
@@ -103,7 +129,8 @@ contract Marketplace is IMarketplace, Ownable {
"invalid maximum rental days"
);
require(
args.rentCurrency == NATIVE_TOKEN || supportedRentCurrencies[args.rentCurrency],
args.rentCurrency == NATIVE_TOKEN ||
supportedRentCurrencies[args.rentCurrency],
"unsupported rent currency"
);

@@ -122,28 +149,45 @@ contract Marketplace is IMarketplace, Ownable {
address(this),
args.tokenId
);

emit List(
msg.sender,
args.product,
accessToken,
args.tokenId,
args.minRentalDays,
args.maxRentalDays,
args.rentCurrency,
args.dailyRent,
args.rentRecipient
);
}

/**
* @inheritdoc IMarketplace
*/
function delist(DelistArgs memory args) public {
ListingInfo storage listing = _listings[args.accessToken][
args.tokenId
];
ListingInfo storage listing = _listings[args.accessToken][args.tokenId];
require(listing.owner == msg.sender, "not listing owner");
listing.status = ListingStatus.Delisted;

emit Delist(msg.sender, args.accessToken, args.tokenId);
}

/**
* @inheritdoc IMarketplace
*/
function relist(RelistArgs memory args) public {
ListingInfo storage listing = _listings[args.accessToken][
args.tokenId
];
ListingInfo storage listing = _listings[args.accessToken][args.tokenId];
require(listing.owner == msg.sender, "not listing owner");
require(args.minRentalDays > 0, "invalid minimum rental days");
require(
args.maxRentalDays >= args.minRentalDays,
"invalid maximum rental days"
);
require(
args.rentCurrency == NATIVE_TOKEN || supportedRentCurrencies[args.rentCurrency],
args.rentCurrency == NATIVE_TOKEN ||
supportedRentCurrencies[args.rentCurrency],
"unsupported rent currency"
);

@@ -153,8 +197,22 @@ contract Marketplace is IMarketplace, Ownable {
listing.dailyRent = args.dailyRent;
listing.rentRecipient = args.rentRecipient;
listing.status = ListingStatus.Listing;

emit Relist(
msg.sender,
args.accessToken,
args.tokenId,
args.minRentalDays,
args.maxRentalDays,
args.rentCurrency,
args.dailyRent,
args.rentRecipient
);
}

/**
* @inheritdoc IMarketplace
*/
function rent(RentArgs memory args) public payable {
require(
_rentals[args.accessToken][args.tokenId][args.tenant].status ==
@@ -190,12 +248,20 @@ contract Marketplace is IMarketplace, Ownable {
args.prepaidRent
);
// Mint access token to tenant
AccessToken(args.accessToken).mint(
AccessToken(args.accessToken).mint(args.tenant, args.tokenId);

emit Rent(
args.tenant,
args.tokenId
args.accessToken,
args.tokenId,
args.rentalDays,
args.prepaidRent
);
}

/**
* @inheritdoc IMarketplace
*/
function payRent(PayRentArgs memory args) public payable {
ListingInfo memory listing = _listings[args.accessToken][args.tokenId];
RentalInfo storage rental = _rentals[args.accessToken][args.tokenId][
@@ -209,8 +275,18 @@ contract Marketplace is IMarketplace, Ownable {

// Pay rent
_payRent(listing, rental, args.rent);

emit PayRent(
args.tenant,
args.accessToken,
args.tokenId,
args.rent
);
}

/**
* @inheritdoc IMarketplace
*/
function endLease(EndLeaseArgs memory args) public {
RentalInfo storage rental = _rentals[args.accessToken][args.tokenId][
args.tenant
@@ -227,12 +303,20 @@ contract Marketplace is IMarketplace, Ownable {
// Burn tenant's access token
AccessToken(args.accessToken).burn(args.tokenId);
rental.status = RentalStatus.EndedOrNotExist;

emit EndLease(
args.tenant,
args.accessToken,
args.tokenId,
msg.sender
);
}

/**
* @inheritdoc IMarketplace
*/
function withdraw(WithdrawArgs memory args) public {
ListingInfo storage listing = _listings[args.accessToken][
args.tokenId
];
ListingInfo storage listing = _listings[args.accessToken][args.tokenId];
require(listing.owner == msg.sender, "not listing owner");
require(
!AccessToken(args.accessToken).isExist(args.tokenId),
@@ -245,6 +329,12 @@ contract Marketplace is IMarketplace, Ownable {
listing.owner,
args.tokenId
);

emit Withdraw(
msg.sender,
args.accessToken,
args.tokenId
);
}

function _payRent(
40 changes: 40 additions & 0 deletions contracts/interfaces/IMarketplace.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {IMarketplaceStructs} from "./IMarketplaceStructs.sol";
import {IMarketplaceEvents} from "./IMarketplaceEvents.sol";

interface IMarketplace is IMarketplaceStructs, IMarketplaceEvents {
function getListingInfo(
address accessToken,
uint256 tokenId
) external view returns (ListingInfo memory);

function getRentalInfo(
address accessToken,
uint256 tokenId,
address tenant
) external view returns (RentalInfo memory);

function setFeePoints(uint256 feePoints) external;

function setTreasury(address payable treasury) external;

function addRentCurrencies(address[] memory rentCurrencies) external;

function removeRentCurrencies(address[] memory rentCurrencies) external;

function list(ListArgs memory args) external;

function delist(DelistArgs memory args) external;

function relist(RelistArgs memory args) external;

function rent(RentArgs memory args) external payable;

function payRent(PayRentArgs memory args) external payable;

function endLease(EndLeaseArgs memory args) external;

function withdraw(WithdrawArgs memory args) external;
}
61 changes: 61 additions & 0 deletions contracts/interfaces/IMarketplaceEvents.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

interface IMarketplaceEvents {
event List(
address indexed owner,
address indexed product,
address accessToken,
uint256 indexed tokenId,
uint256 minRentalDays,
uint256 maxRentalDays,
address rentCurrency,
uint256 dailyRent,
address rentRecipient
);

event Delist(
address indexed owner,
address indexed accessToken,
uint256 indexed tokenId
);

event Relist(
address indexed owner,
address indexed accessToken,
uint256 indexed tokenId,
uint256 minRentalDays,
uint256 maxRentalDays,
address rentCurrency,
uint256 dailyRent,
address rentRecipient
);

event Rent(
address indexed tenant,
address indexed accessToken,
uint256 indexed tokenId,
uint256 rentalDays,
uint256 prepaidRent
);

event PayRent(
address indexed tenant,
address indexed accessToken,
uint256 indexed tokenId,
uint256 rent
);

event EndLease(
address indexed tenant,
address indexed accessToken,
uint256 indexed tokenId,
address operator
);

event Withdraw(
address indexed owner,
address indexed accessToken,
uint256 indexed tokenId
);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

interface IMarketplace {
interface IMarketplaceStructs {
// Statuses of a listing. WithdrawnOrNotExist, which is 0, is effectively the same as never listed before.
enum ListingStatus {
WithdrawnOrNotExist,
File renamed without changes.
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[profile.default]
src = "src"
src = "contracts"
out = "out"
libs = ["dependencies"]
auto_detect_remappings = true
4 changes: 2 additions & 2 deletions test/AccessToken.t.sol
Original file line number Diff line number Diff line change
@@ -2,8 +2,8 @@
pragma solidity ^0.8.24;

import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
import {AccessToken} from "../src/AccessToken.sol";
import {IProduct} from "../src/interfaces/IProduct.sol";
import {AccessToken} from "../contracts/AccessToken.sol";
import {IProduct} from "../contracts/interfaces/IProduct.sol";
import {MockProduct} from "./mocks/MockProduct.sol";
import "forge-std/src/Test.sol";

6 changes: 3 additions & 3 deletions test/AccessTokenFactory.t.sol
Original file line number Diff line number Diff line change
@@ -3,9 +3,9 @@ pragma solidity ^0.8.24;

import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {AccessTokenFactory} from "../src/AccessTokenFactory.sol";
import {AccessToken} from "../src/AccessToken.sol";
import {IProduct} from "../src/interfaces/IProduct.sol";
import {AccessTokenFactory} from "../contracts/AccessTokenFactory.sol";
import {AccessToken} from "../contracts/AccessToken.sol";
import {IProduct} from "../contracts/interfaces/IProduct.sol";
import {MockProduct} from "./mocks/MockProduct.sol";
import "forge-std/src/Test.sol";

62 changes: 31 additions & 31 deletions test/Marketplace.t.sol
Original file line number Diff line number Diff line change
@@ -3,11 +3,11 @@ pragma solidity ^0.8.24;

import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {AccessTokenFactory} from "../src/AccessTokenFactory.sol";
import {IMarketplace} from "../src/interfaces/IMarketplace.sol";
import {Marketplace} from "../src/Marketplace.sol";
import {AccessToken} from "../src/AccessToken.sol";
import {IProduct} from "../src/interfaces/IProduct.sol";
import {AccessTokenFactory} from "../contracts/AccessTokenFactory.sol";
import {IMarketplaceStructs} from "../contracts/interfaces/IMarketplaceStructs.sol";
import {Marketplace} from "../contracts/Marketplace.sol";
import {AccessToken} from "../contracts/AccessToken.sol";
import {IProduct} from "../contracts/interfaces/IProduct.sol";
import {MockProduct} from "./mocks/MockProduct.sol";
import "forge-std/src/Test.sol";

@@ -43,7 +43,7 @@ contract AccessTokenFactoryTest is Test {
uint256 tokenId = product.mint(address(this));

// List the product token on the marketplace
IMarketplace.ListArgs memory args = IMarketplace.ListArgs({
IMarketplaceStructs.ListArgs memory args = IMarketplaceStructs.ListArgs({
product: productAddress,
tokenId: tokenId,
minRentalDays: 1,
@@ -57,7 +57,7 @@ contract AccessTokenFactoryTest is Test {
marketplace.list(args);

// Check that the token is listed
IMarketplace.ListingInfo memory listing = marketplace.getListingInfo(
IMarketplaceStructs.ListingInfo memory listing = marketplace.getListingInfo(
address(factory.getAccessToken(productAddress)),
tokenId
);
@@ -68,7 +68,7 @@ contract AccessTokenFactoryTest is Test {
assertEq(listing.dailyRent, 1 ether);
assertEq(
uint256(listing.status),
uint256(IMarketplace.ListingStatus.Listing)
uint256(IMarketplaceStructs.ListingStatus.Listing)
);
}

@@ -77,7 +77,7 @@ contract AccessTokenFactoryTest is Test {
uint256 tokenId = product.mint(address(this));

// List the product token on the marketplace
IMarketplace.ListArgs memory listArgs = IMarketplace.ListArgs({
IMarketplaceStructs.ListArgs memory listArgs = IMarketplaceStructs.ListArgs({
product: productAddress,
tokenId: tokenId,
minRentalDays: 1,
@@ -91,21 +91,21 @@ contract AccessTokenFactoryTest is Test {
marketplace.list(listArgs);

// Delist the token
IMarketplace.DelistArgs memory delistArgs = IMarketplace.DelistArgs({
IMarketplaceStructs.DelistArgs memory delistArgs = IMarketplaceStructs.DelistArgs({
accessToken: payable(factory.getAccessToken(productAddress)),
tokenId: tokenId
});

marketplace.delist(delistArgs);

// Check that the token is delisted
IMarketplace.ListingInfo memory listing = marketplace.getListingInfo(
IMarketplaceStructs.ListingInfo memory listing = marketplace.getListingInfo(
address(factory.getAccessToken(productAddress)),
tokenId
);
assertEq(
uint256(listing.status),
uint256(IMarketplace.ListingStatus.Delisted)
uint256(IMarketplaceStructs.ListingStatus.Delisted)
);
}

@@ -117,7 +117,7 @@ contract AccessTokenFactoryTest is Test {
vm.deal(tenant, 10 ether);

// List the product token on the marketplace
IMarketplace.ListArgs memory listArgs = IMarketplace.ListArgs({
IMarketplaceStructs.ListArgs memory listArgs = IMarketplaceStructs.ListArgs({
product: productAddress,
tokenId: tokenId,
minRentalDays: 1,
@@ -131,7 +131,7 @@ contract AccessTokenFactoryTest is Test {
marketplace.list(listArgs);

// Rent the token
IMarketplace.RentArgs memory rentArgs = IMarketplace.RentArgs({
IMarketplaceStructs.RentArgs memory rentArgs = IMarketplaceStructs.RentArgs({
accessToken: factory.getAccessToken(productAddress),
tokenId: tokenId,
tenant: tenant,
@@ -143,7 +143,7 @@ contract AccessTokenFactoryTest is Test {
marketplace.rent{value: 5 ether}(rentArgs);

// Check that the token is rented
IMarketplace.RentalInfo memory rental = marketplace.getRentalInfo(
IMarketplaceStructs.RentalInfo memory rental = marketplace.getRentalInfo(
address(factory.getAccessToken(productAddress)),
tokenId,
tenant
@@ -154,7 +154,7 @@ contract AccessTokenFactoryTest is Test {
assertEq(rental.dailyRent, 1 ether);
assertEq(
uint256(rental.status),
uint256(IMarketplace.RentalStatus.Renting)
uint256(IMarketplaceStructs.RentalStatus.Renting)
);
}

@@ -166,7 +166,7 @@ contract AccessTokenFactoryTest is Test {
vm.deal(tenant, 20 ether);

// List the product token on the marketplace
IMarketplace.ListArgs memory listArgs = IMarketplace.ListArgs({
IMarketplaceStructs.ListArgs memory listArgs = IMarketplaceStructs.ListArgs({
product: productAddress,
tokenId: tokenId,
minRentalDays: 1,
@@ -180,7 +180,7 @@ contract AccessTokenFactoryTest is Test {
marketplace.list(listArgs);

// Rent the token
IMarketplace.RentArgs memory rentArgs = IMarketplace.RentArgs({
IMarketplaceStructs.RentArgs memory rentArgs = IMarketplaceStructs.RentArgs({
accessToken: factory.getAccessToken(productAddress),
tokenId: tokenId,
tenant: tenant,
@@ -191,7 +191,7 @@ contract AccessTokenFactoryTest is Test {
vm.prank(tenant);
marketplace.rent{value: 5 ether}(rentArgs);

IMarketplace.RentalInfo memory rental = marketplace.getRentalInfo(
IMarketplaceStructs.RentalInfo memory rental = marketplace.getRentalInfo(
address(factory.getAccessToken(productAddress)),
tokenId,
tenant
@@ -203,11 +203,11 @@ contract AccessTokenFactoryTest is Test {
assertEq(rental.totalPaidRent, 5 ether);
assertEq(
uint256(rental.status),
uint256(IMarketplace.RentalStatus.Renting)
uint256(IMarketplaceStructs.RentalStatus.Renting)
);

// Pay additional rent
IMarketplace.PayRentArgs memory payRentArgs = IMarketplace.PayRentArgs({
IMarketplaceStructs.PayRentArgs memory payRentArgs = IMarketplaceStructs.PayRentArgs({
accessToken: factory.getAccessToken(productAddress),
tokenId: tokenId,
tenant: tenant,
@@ -235,7 +235,7 @@ contract AccessTokenFactoryTest is Test {
vm.deal(tenant, 10 ether);

// List the product token on the marketplace
IMarketplace.ListArgs memory listArgs = IMarketplace.ListArgs({
IMarketplaceStructs.ListArgs memory listArgs = IMarketplaceStructs.ListArgs({
product: productAddress,
tokenId: tokenId,
minRentalDays: 1,
@@ -249,7 +249,7 @@ contract AccessTokenFactoryTest is Test {
marketplace.list(listArgs);

// Rent the token
IMarketplace.RentArgs memory rentArgs = IMarketplace.RentArgs({
IMarketplaceStructs.RentArgs memory rentArgs = IMarketplaceStructs.RentArgs({
accessToken: factory.getAccessToken(productAddress),
tokenId: tokenId,
tenant: tenant,
@@ -264,7 +264,7 @@ contract AccessTokenFactoryTest is Test {
vm.warp(block.timestamp + 6 days);

// End the lease
IMarketplace.EndLeaseArgs memory endLeaseArgs = IMarketplace
IMarketplaceStructs.EndLeaseArgs memory endLeaseArgs = IMarketplaceStructs
.EndLeaseArgs({
accessToken: factory.getAccessToken(productAddress),
tokenId: tokenId,
@@ -275,14 +275,14 @@ contract AccessTokenFactoryTest is Test {
marketplace.endLease(endLeaseArgs);

// Check that the lease is ended
IMarketplace.RentalInfo memory rental = marketplace.getRentalInfo(
IMarketplaceStructs.RentalInfo memory rental = marketplace.getRentalInfo(
address(factory.getAccessToken(productAddress)),
tokenId,
tenant
);
assertEq(
uint256(rental.status),
uint256(IMarketplace.RentalStatus.EndedOrNotExist)
uint256(IMarketplaceStructs.RentalStatus.EndedOrNotExist)
);
}

@@ -291,7 +291,7 @@ contract AccessTokenFactoryTest is Test {
uint256 tokenId = product.mint(address(this));

// List the product token on the marketplace
IMarketplace.ListArgs memory listArgs = IMarketplace.ListArgs({
IMarketplaceStructs.ListArgs memory listArgs = IMarketplaceStructs.ListArgs({
product: productAddress,
tokenId: tokenId,
minRentalDays: 1,
@@ -305,15 +305,15 @@ contract AccessTokenFactoryTest is Test {
marketplace.list(listArgs);

// Delist the token
IMarketplace.DelistArgs memory delistArgs = IMarketplace.DelistArgs({
IMarketplaceStructs.DelistArgs memory delistArgs = IMarketplaceStructs.DelistArgs({
accessToken: payable(factory.getAccessToken(productAddress)),
tokenId: tokenId
});

marketplace.delist(delistArgs);

// Withdraw the token
IMarketplace.WithdrawArgs memory withdrawArgs = IMarketplace
IMarketplaceStructs.WithdrawArgs memory withdrawArgs = IMarketplaceStructs
.WithdrawArgs({
accessToken: factory.getAccessToken(productAddress),
tokenId: tokenId
@@ -322,13 +322,13 @@ contract AccessTokenFactoryTest is Test {
marketplace.withdraw(withdrawArgs);

// Check that the token is withdrawn
IMarketplace.ListingInfo memory listing = marketplace.getListingInfo(
IMarketplaceStructs.ListingInfo memory listing = marketplace.getListingInfo(
address(factory.getAccessToken(productAddress)),
tokenId
);
assertEq(
uint256(listing.status),
uint256(IMarketplace.ListingStatus.WithdrawnOrNotExist)
uint256(IMarketplaceStructs.ListingStatus.WithdrawnOrNotExist)
);
}

2 changes: 1 addition & 1 deletion test/mocks/MockProduct.sol
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Ini
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {IProduct} from "../../src/interfaces/IProduct.sol";
import {IProduct} from "../../contracts/interfaces/IProduct.sol";

contract MockProduct is
Initializable,