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-872: delist rental offer #51

Merged
merged 5 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
183 changes: 183 additions & 0 deletions .openzeppelin/polygon.json
Original file line number Diff line number Diff line change
Expand Up @@ -3664,6 +3664,189 @@
},
"namespaces": {}
}
},
"1f246305d6ec0d1bd26f5b4e4dd2ede7f80752e3687b10f0406bda163f6efff0": {
"address": "0x39F2fF9D3E4A403B617a8f05DE61d3C8081e857b",
"txHash": "0x54e5c816b3fdfb7ff02cb9cc978f9cf5e24311a316d5f63e89f931e5093e98e5",
"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:22"
},
{
"label": "isCreated",
"offset": 0,
"slot": "152",
"type": "t_mapping(t_bytes32,t_bool)",
"contract": "OriumSftMarketplace",
"src": "contracts/OriumSftMarketplace.sol:25"
},
{
"label": "nonceDeadline",
"offset": 0,
"slot": "153",
"type": "t_mapping(t_address,t_mapping(t_uint256,t_uint64))",
"contract": "OriumSftMarketplace",
"src": "contracts/OriumSftMarketplace.sol:28"
},
{
"label": "commitmentIdToNonce",
"offset": 0,
"slot": "154",
"type": "t_mapping(t_address,t_mapping(t_uint256,t_uint256))",
"contract": "OriumSftMarketplace",
"src": "contracts/OriumSftMarketplace.sol:31"
},
{
"label": "rentals",
"offset": 0,
"slot": "155",
"type": "t_mapping(t_bytes32,t_struct(Rental)1080_storage)",
"contract": "OriumSftMarketplace",
"src": "contracts/OriumSftMarketplace.sol:34"
}
],
"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)1080_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)1080_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": {}
}
}
}
}
4 changes: 2 additions & 2 deletions addresses/polygon/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@
"OriumSftMarketplace": {
"address": "0xB1D47B09aa6D81d7B00C3A37705a6A157B83C49F",
"operator": "0x359E1208DE02Af11461A37D72165Ef2dcD2Adfc8",
"implementation": "0xd04d97d855A24b4E70D5723Bb90C194EC8946938",
"implementation": "0x28c0DFe3e6B53ff3A25B9655A0B9D62120Ae34Fc",
"proxyAdmin": "0x48c769f6a8de57d824f0e7330d7A27dee53a43cD",
"libraries": {
"LibOriumSftMarketplace": "0x69C4c9D8B8D815c57Cb252f228159b2794094a17"
"LibOriumSftMarketplace": "0xF992Cf6815aC9c08B3631b5587e16d561ef9a969"
}
},
"ERC7432WrapperForERC4907": {
Expand Down
36 changes: 36 additions & 0 deletions contracts/OriumSftMarketplace.sol
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,26 @@ contract OriumSftMarketplace is Initializable, OwnableUpgradeable, PausableUpgra
emit RentalOfferCancelled(_offer.lender, _offer.nonce);
}

/**
* @notice Delist a rental offer.
* @param _offer The rental offer struct. It should be the same as the one used to create the offer.
*/
function delistRentalOffer(RentalOffer calldata _offer) external whenNotPaused {
_delistRentalOffer(_offer);
}

/**
* @notice Delist a rental offer and withdraw it.
* @param _offer The rental offer struct. It should be the same as the one used to create the offer.
*/
function delistRentalOfferAndWithdraw(RentalOffer calldata _offer) external whenNotPaused {
_delistRentalOffer(_offer);
IERC7589 _rolesRegistry = IERC7589(
IOriumMarketplaceRoyalties(oriumMarketplaceRoyalties).sftRolesRegistryOf(_offer.tokenAddress)
);
_rolesRegistry.releaseTokens(_offer.commitmentId);
}

/**
* @notice Ends the rental prematurely.
* @dev Can only be called by the borrower.
Expand Down Expand Up @@ -426,6 +446,22 @@ contract OriumSftMarketplace is Initializable, OwnableUpgradeable, PausableUpgra
);
}

/**
* @dev Cancels a rental offer.
* @param _offer The rental offer struct.
*/
function _delistRentalOffer(RentalOffer calldata _offer) internal {
bytes32 _offerHash = LibOriumSftMarketplace.hashRentalOffer(_offer);
require(isCreated[_offerHash], 'OriumSftMarketplace: Offer not created');
require(msg.sender == _offer.lender, 'OriumSftMarketplace: Only lender can cancel a rental offer');
require(
nonceDeadline[_offer.lender][_offer.nonce] > block.timestamp,
'OriumSftMarketplace: Nonce expired or not used yet'
);
nonceDeadline[msg.sender][_offer.nonce] = uint64(block.timestamp);
emit RentalOfferCancelled(_offer.lender, _offer.nonce);
}

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

/** ######### Setters ########### **/
Expand Down
87 changes: 87 additions & 0 deletions test/OriumSftMarketplace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,93 @@ describe('OriumSftMarketplace', () => {
})
})

describe('Delist Rental Offer and Withdraw', async () => {
it('Should delist a rental offer and releaseTokens from rolesRegistry', async () => {
await expect(marketplace.connect(lender).delistRentalOfferAndWithdraw(rentalOffer))
.to.emit(marketplace, 'RentalOfferCancelled')
.withArgs(rentalOffer.lender, rentalOffer.nonce)
.to.emit(rolesRegistry, 'TokensReleased')
.withArgs(rentalOffer.commitmentId)
})
it('Should NOT delist a rental offer if tokens was released before directly from registry', async () => {
await rolesRegistry.connect(lender).releaseTokens(rentalOffer.commitmentId)
await expect(marketplace.connect(lender).delistRentalOfferAndWithdraw(rentalOffer)).to.be.revertedWith(
'SftRolesRegistry: account not approved',
)
})
it('Should NOT delist a rental offer if contract is paused', async () => {
await marketplace.connect(operator).pause()
await expect(marketplace.connect(borrower).delistRentalOfferAndWithdraw(rentalOffer)).to.be.revertedWith(
'Pausable: paused',
)
})
it('Should NOT delist a rental offer if nonce not used yet by caller', async () => {
await expect(
marketplace.connect(notOperator).delistRentalOfferAndWithdraw(rentalOffer),
).to.be.revertedWith('OriumSftMarketplace: Only lender can cancel a rental offer')
})
it("Should NOT delist a rental offer after deadline's expiration", async () => {
// move forward in time to expire the offer
const blockTimestamp = (await ethers.provider.getBlock('latest'))?.timestamp
const timeToMove = rentalOffer.deadline - Number(blockTimestamp) + 1
await ethers.provider.send('evm_increaseTime', [timeToMove])

await expect(marketplace.connect(lender).delistRentalOfferAndWithdraw(rentalOffer)).to.be.revertedWith(
'OriumSftMarketplace: Nonce expired or not used yet',
)
})
it("Should NOT delist a rental offer if it's not created", async () => {
await expect(
marketplace
.connect(lender)
.delistRentalOfferAndWithdraw({ ...rentalOffer, nonce: `0x${randomBytes(32).toString('hex')}` }),
).to.be.revertedWith('OriumSftMarketplace: Offer not created')
})
})

describe('Delist Rental Offer', async () => {
it('Should delist a rental offer and releaseTokens from rolesRegistry', async () => {
await expect(marketplace.connect(lender).delistRentalOffer(rentalOffer))
.to.emit(marketplace, 'RentalOfferCancelled')
.withArgs(rentalOffer.lender, rentalOffer.nonce)
.to.not.emit(rolesRegistry, 'TokensReleased')
})
it('Should delist a rental offer if tokens was released before directly from registry', async () => {
await rolesRegistry.connect(lender).releaseTokens(rentalOffer.commitmentId)
await expect(marketplace.connect(lender).delistRentalOffer(rentalOffer))
.to.emit(marketplace, 'RentalOfferCancelled')
.withArgs(rentalOffer.lender, rentalOffer.nonce)
})
it('Should NOT delist a rental offer if contract is paused', async () => {
await marketplace.connect(operator).pause()
await expect(marketplace.connect(borrower).delistRentalOffer(rentalOffer)).to.be.revertedWith(
'Pausable: paused',
)
})
it('Should NOT delist a rental offer if nonce not used yet by caller', async () => {
await expect(marketplace.connect(notOperator).delistRentalOffer(rentalOffer)).to.be.revertedWith(
'OriumSftMarketplace: Only lender can cancel a rental offer',
)
})
it("Should NOT delist a rental offer after deadline's expiration", async () => {
// move forward in time to expire the offer
const blockTimestamp = (await ethers.provider.getBlock('latest'))?.timestamp
const timeToMove = rentalOffer.deadline - Number(blockTimestamp) + 1
await ethers.provider.send('evm_increaseTime', [timeToMove])

await expect(marketplace.connect(lender).delistRentalOffer(rentalOffer)).to.be.revertedWith(
'OriumSftMarketplace: Nonce expired or not used yet',
)
})
it("Should NOT delist a rental offer if it's not created", async () => {
await expect(
marketplace
.connect(lender)
.delistRentalOffer({ ...rentalOffer, nonce: `0x${randomBytes(32).toString('hex')}` }),
).to.be.revertedWith('OriumSftMarketplace: Offer not created')
})
})

describe('Batch Release Tokens', async () => {
it('Should release tokens from rolesRegistry', async () => {
await time.increase(ONE_DAY)
Expand Down
Loading