Skip to content

Commit

Permalink
test: three main contracts
Browse files Browse the repository at this point in the history
  • Loading branch information
CanvasL committed Jul 30, 2024
1 parent 78df433 commit 0b775aa
Show file tree
Hide file tree
Showing 7 changed files with 552 additions and 20 deletions.
6 changes: 5 additions & 1 deletion src/AccessToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ contract AccessToken is ERC721 {
}

modifier onlyTokenOwner(uint256 tokenId) {
require(msg.sender == PRODUCT.ownerOf(tokenId), "not token owner");
require(msg.sender == PRODUCT.ownerOf(tokenId), "not product owner");
_;
}

Expand All @@ -30,4 +30,8 @@ contract AccessToken is ERC721 {
function burn(uint256 tokenId) external onlyTokenOwner(tokenId) {
_burn(tokenId);
}

function isExist(uint256 tokenId) external view returns (bool) {
return _ownerOf(tokenId) != address(0);
}
}
2 changes: 1 addition & 1 deletion src/AccessTokenFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ contract AccessTokenFactory {
AccessToken accessToken = new AccessToken(
IProduct(productAddress),
ERC721(productAddress).name(),
ERC721(productAddress).symbol()
string(abi.encodePacked("AC.", ERC721(productAddress).symbol()))
);
getAccessToken[productAddress] = address(accessToken);
return address(accessToken);
Expand Down
44 changes: 26 additions & 18 deletions src/Marketplace.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ contract Marketplace is IMarketplace, Ownable {
/**
* @notice access token => token id => listing info
*/
mapping(address => mapping(uint256 => ListingInfo)) public listings;
mapping(address => mapping(uint256 => ListingInfo)) internal _listings;

/**
* @notice access token => token id => tenant => rent info
*/
mapping(address => mapping(uint256 => mapping(address => RentalInfo)))
public rentals;
internal _rentals;

constructor(
address initialOwner,
Expand All @@ -53,6 +53,14 @@ contract Marketplace is IMarketplace, Ownable {
_feePoints = feePoints;
}

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) {
return _rentals[accessToken][tokenId][tenant];
}

function setFeePoints(uint256 feePoints) external onlyOwner {
_feePoints = feePoints;
}
Expand Down Expand Up @@ -85,7 +93,7 @@ contract Marketplace is IMarketplace, Ownable {
accessToken = ACCESS_TOKEN_FACTORY.createAccessToken(args.product);
}
require(
listings[accessToken][args.tokenId].status ==
_listings[accessToken][args.tokenId].status ==
ListingStatus.WithdrawnOrNotExist, // Never listed or withdrawn
"token already listed"
);
Expand All @@ -95,11 +103,11 @@ contract Marketplace is IMarketplace, Ownable {
"invalid maximum rental days"
);
require(
supportedRentCurrencies[args.rentCurrency],
args.rentCurrency == NATIVE_TOKEN || supportedRentCurrencies[args.rentCurrency],
"unsupported rent currency"
);

listings[accessToken][args.tokenId] = ListingInfo({
_listings[accessToken][args.tokenId] = ListingInfo({
owner: msg.sender,
minRentalDays: args.minRentalDays,
maxRentalDays: args.maxRentalDays,
Expand All @@ -117,15 +125,15 @@ contract Marketplace is IMarketplace, Ownable {
}

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

function relist(RelistArgs memory args) public {
ListingInfo storage listing = listings[args.accessToken][
ListingInfo storage listing = _listings[args.accessToken][
args.tokenId
];
require(listing.owner == msg.sender, "not listing owner");
Expand All @@ -135,7 +143,7 @@ contract Marketplace is IMarketplace, Ownable {
"invalid maximum rental days"
);
require(
supportedRentCurrencies[args.rentCurrency],
args.rentCurrency == NATIVE_TOKEN || supportedRentCurrencies[args.rentCurrency],
"unsupported rent currency"
);

Expand All @@ -149,12 +157,12 @@ contract Marketplace is IMarketplace, Ownable {

function rent(RentArgs memory args) public payable {
require(
rentals[args.accessToken][args.tokenId][args.tenant].status ==
_rentals[args.accessToken][args.tokenId][args.tenant].status ==
RentalStatus.EndedOrNotExist,
"existing rental"
);

ListingInfo memory listing = listings[args.accessToken][args.tokenId];
ListingInfo memory listing = _listings[args.accessToken][args.tokenId];
require(
listing.minRentalDays <= args.rentalDays &&
args.rentalDays <= listing.maxRentalDays,
Expand All @@ -165,7 +173,7 @@ contract Marketplace is IMarketplace, Ownable {
"insufficient prepaid rent"
);

rentals[args.accessToken][args.tokenId][args.tenant] = RentalInfo({
_rentals[args.accessToken][args.tokenId][args.tenant] = RentalInfo({
startTime: block.timestamp,
endTime: block.timestamp + args.rentalDays * 1 days,
rentalDays: args.rentalDays,
Expand All @@ -178,7 +186,7 @@ contract Marketplace is IMarketplace, Ownable {
// Pay rent
_payRent(
listing,
rentals[args.accessToken][args.tokenId][args.tenant],
_rentals[args.accessToken][args.tokenId][args.tenant],
args.prepaidRent
);
// Mint access token to tenant
Expand All @@ -189,8 +197,8 @@ contract Marketplace is IMarketplace, Ownable {
}

function payRent(PayRentArgs memory args) public payable {
ListingInfo memory listing = listings[args.accessToken][args.tokenId];
RentalInfo storage rental = rentals[args.accessToken][args.tokenId][
ListingInfo memory listing = _listings[args.accessToken][args.tokenId];
RentalInfo storage rental = _rentals[args.accessToken][args.tokenId][
args.tenant
];
require(
Expand All @@ -204,7 +212,7 @@ contract Marketplace is IMarketplace, Ownable {
}

function endLease(EndLeaseArgs memory args) public {
RentalInfo storage rental = rentals[args.accessToken][args.tokenId][
RentalInfo storage rental = _rentals[args.accessToken][args.tokenId][
args.tenant
];
// The lease can be ended only if the term is over or the rent is insufficient
Expand All @@ -222,17 +230,17 @@ contract Marketplace is IMarketplace, Ownable {
}

function withdraw(WithdrawArgs memory args) public {
ListingInfo storage listing = listings[args.accessToken][
ListingInfo storage listing = _listings[args.accessToken][
args.tokenId
];
require(listing.owner == msg.sender, "not listing owner");
require(
AccessToken(args.accessToken).ownerOf(args.tokenId) == address(0),
!AccessToken(args.accessToken).isExist(args.tokenId),
"access token has tenant"
);
listing.status = ListingStatus.WithdrawnOrNotExist;

(AccessToken(args.accessToken).PRODUCT()).safeTransferFrom(
(AccessToken(args.accessToken).PRODUCT()).transferFrom(
address(this),
listing.owner,
args.tokenId
Expand Down
80 changes: 80 additions & 0 deletions test/AccessToken.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// SPDX-License-Identifier: MIT
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 {MockProduct} from "./mocks/MockProduct.sol";
import "forge-std/src/Test.sol";

contract AccessTokenTest is Test {
AccessToken accessToken;
MockProduct product;

function setUp() public {
MockProduct productImpl = new MockProduct();
product = MockProduct(Clones.clone(address(productImpl)));
product.initialize("MockProduct", "MP", "https://examples.com");

accessToken = new AccessToken(
IProduct(address(product)),
"AccessToken",
"AC"
);
}

function testMint() public {
vm.prank(address(this));
uint256 tokenId = product.mint(address(this));

address user = makeAddr("user");
accessToken.mint(user, tokenId);

assertEq(accessToken.ownerOf(tokenId), user);
}

function testMint_NotProductOwner() public {
vm.prank(address(this));
uint256 tokenId = product.mint(address(this));

address notProductOwner = makeAddr("notProductOwner");
vm.expectRevert("not product owner");
vm.prank(notProductOwner);
accessToken.mint(notProductOwner, tokenId);
}

function testBurn() public {
vm.prank(address(this));
uint256 tokenId = product.mint(address(this));

accessToken.mint(address(this), tokenId);
accessToken.burn(tokenId);

vm.expectRevert();
accessToken.ownerOf(tokenId);
}

function testBurn_NotProductOwner() public {
vm.prank(address(this));
uint256 tokenId = product.mint(address(this));

accessToken.mint(address(this), tokenId);

address notProductOwner = makeAddr("notProductOwner");
vm.prank(notProductOwner);
vm.expectRevert("not product owner");
accessToken.burn(tokenId);
}

function testMint_AfterBurned() public {
vm.prank(address(this));
uint256 tokenId = product.mint(address(this));

address user1 = makeAddr("user1");
address user2 = makeAddr("user2");
accessToken.mint(user1, tokenId);
accessToken.burn(tokenId);
accessToken.mint(user2, tokenId);
assertEq(accessToken.ownerOf(tokenId), user2);
}
}
44 changes: 44 additions & 0 deletions test/AccessTokenFactory.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT
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 {MockProduct} from "./mocks/MockProduct.sol";
import "forge-std/src/Test.sol";

contract AccessTokenFactoryTest is Test {
AccessTokenFactory factory;
MockProduct product;

function setUp() public {
factory = new AccessTokenFactory();
MockProduct productImpl = new MockProduct();
product = MockProduct(Clones.clone(address(productImpl)));
product.initialize("MockProduct", "MP", "https://examples.com");
}

function testCreateAccessToken() public {
address productAddress = address(product);

assertEq(factory.getAccessToken(productAddress), address(0));

address accessTokenAddress = factory.createAccessToken(productAddress);
assertTrue(accessTokenAddress != address(0));
assertEq(factory.getAccessToken(productAddress), accessTokenAddress);

AccessToken accessToken = AccessToken(accessTokenAddress);
assertEq(accessToken.name(), "MockProduct");
assertEq(accessToken.symbol(), "AC.MP");
}

function testCreateAccessTokenTwice() public {
address productAddress = address(product);
factory.createAccessToken(productAddress);

vm.expectRevert("existing access token");
factory.createAccessToken(productAddress);
}
}
Loading

0 comments on commit 0b775aa

Please sign in to comment.