Description:
Users can rent NFTs from the NFTBank. They have to pay a fixed commission when they want to get rent, and when they want to return NFT, the contract will take a second fee, depending on how many days you have NFT from.
How to Submit a Solution:
Use the below submission form to submit the solution: https://quillaudits.typeform.com/QuillCTF
Objective of CTF
You rent an NFT. After 10 days have passed, if you should hack the contract, and finally have the NFT, the contract should not show, that you have debt.
Note: You can create POCs using Foundry/Hardhat. Without proper POC, your submissions will not be accepted.
Contract Code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
import {IERC721} from "openzeppelin-contracts/contracts/token/ERC721/IERC721.sol";
import {ERC721Holder} from "openzeppelin-contracts/contracts/token/ERC721/utils/ERC721Holder.sol";
import {ReentrancyGuard} from "openzeppelin-contracts/contracts/security/ReentrancyGuard.sol";
contract NFTBank is ReentrancyGuard, ERC721Holder {
struct rentData {
address collection;
uint id;
uint startDate;
}
struct nftData {
address owner;
uint rentFeePerDay;
uint startRentFee;
}
mapping(address => mapping(uint => nftData)) public nfts;
mapping(address => mapping(uint => uint)) public collectedFee;
rentData[] public rentNFTs;
error WrongETHValue();
error YouAreNotOwner();
function addNFT(
address collection,
uint id,
uint rentFeePerDay,
uint startRentFee
) external {
nfts[collection][id] = nftData({
owner: msg.sender,
rentFeePerDay: rentFeePerDay,
startRentFee: startRentFee
});
IERC721(collection).safeTransferFrom(msg.sender, address(this), id);
}
function getBackNft(
address collection,
uint id,
address payable transferFeeTo
) external {
if (msg.sender != nfts[collection][id].owner) revert YouAreNotOwner();
IERC721(collection).safeTransferFrom(address(this), msg.sender, id);
transferFeeTo.transfer(collectedFee[collection][id]);
}
function rent(address collection, uint id) external payable {
IERC721(collection).safeTransferFrom(address(this), msg.sender, id);
if (msg.value != nfts[collection][id].startRentFee)
revert WrongETHValue();
rentNFTs.push(
rentData({
collection: collection,
id: id,
startDate: block.timestamp
})
);
}
function refund(address collection, uint id) external payable nonReentrant {
IERC721(collection).safeTransferFrom(msg.sender, address(this), id);
rentData memory rentedNft = rentData({
collection: address(0),
id: 0,
startDate: 0
});
for (uint i; i < rentNFTs.length; i++) {
if (rentNFTs[i].collection == collection && rentNFTs[i].id == id) {
rentedNft = rentNFTs[i];
}
}
uint daysInRent = (block.timestamp - rentedNft.startDate) / 86400 > 1
? (block.timestamp - rentedNft.startDate) / 86400
: 1;
uint amount = daysInRent * nfts[collection][id].rentFeePerDay;
if (msg.value != amount) revert WrongETHValue();
uint index;
for (uint i; i < rentNFTs.length; i++) {
if (rentNFTs[i].collection == collection && rentNFTs[i].id == id) {
index = i;
}
}
collectedFee[collection][id] += amount;
payable(msg.sender).transfer(
nfts[rentNFTs[index].collection][rentNFTs[index].id].startRentFee
);
rentNFTs[index] = rentNFTs[rentNFTs.length - 1];
rentNFTs.pop();
}
}
Foundry setUp:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
import "forge-std/Test.sol";
import {NFTBank} from "../src/NFTBank.sol";
import {ERC721} from "openzeppelin-contracts/contracts/token/ERC721/ERC721.sol";
import {Ownable} from "openzeppelin-contracts/contracts/access/Ownable.sol";
contract CryptoKitties is ERC721("CryptoKitties", "MEOW"), Ownable {
function mint(address to, uint id) external onlyOwner {
_safeMint(to, id);
}
}
contract NFTBankHack is Test {
NFTBank bank;
CryptoKitties meow;
address nftOwner = makeAddr("nftOwner");
address attacker = makeAddr("attacker");
function setUp() public {
vm.startPrank(nftOwner);
bank = new NFTBank();
meow = new CryptoKitties();
for (uint i; i < 10; i++) {
meow.mint(nftOwner, i);
meow.approve(address(bank), i);
bank.addNFT(address(meow), i, 2 gwei, 500 gwei);
}
vm.stopPrank();
}
function test() public {
vm.deal(attacker, 1 ether);
vm.startPrank(attacker);
bank.rent{value: 500 gwei}(address(meow), 1);
vm.warp(block.timestamp + 86400 * 10);
//solution
vm.stopPrank();
assertEq(attacker.balance, 1 ether);
assertEq(meow.ownerOf(1), attacker);
}
}
Solutions:
Coming Soon…..
Author -
SovaSlava