├── README.md └── contracts └── Rent.sol /README.md: -------------------------------------------------------------------------------- 1 | Rent 2 | -------------------------------------------------------------------------------- /contracts/Rent.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma experimental ABIEncoderV2; 4 | pragma solidity 0.8.10; 5 | 6 | import "../../DigitalGolems/Interfaces/ICard.sol"; 7 | import "../../Digibytes/Interfaces/IBEP20.sol"; 8 | import "../Interfaces/IConservation.sol"; 9 | import "../../Utils/Owner.sol"; 10 | import "@openzeppelin/contracts/utils/Counters.sol"; 11 | import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; 12 | import "./IRent.sol"; 13 | 14 | contract Rent is Owner, ReentrancyGuard, IRent { 15 | using Counters for Counters.Counter; 16 | 17 | Counters.Counter private _itemsListedForRent; 18 | Counters.Counter private _itemsClosedRent; 19 | Counters.Counter private _itemsRented; 20 | Counters.Counter private _items; 21 | 22 | uint32 secondsInADay = 86400; 23 | 24 | ICard public card; 25 | IConservation public conservation; 26 | 27 | struct CardForRent { 28 | uint256 itemID; 29 | uint256 cardID; 30 | address cardRenter; 31 | uint8 timeToRent; // for example user can use it 7 days - secondsInADay * 7 days 32 | bool rented; 33 | bool closed; //deleted from market 34 | uint256 timeWhenRentStarted; 35 | uint256 priceByDay; 36 | uint256 deposit; 37 | uint256 feedPrice; 38 | } 39 | 40 | CardForRent[] marketItems; 41 | mapping(uint256 => uint256) itemIDToFeedDeposit; 42 | uint256 thisDBTBalance; 43 | 44 | IBEP20 public DBT; 45 | 46 | //creating market order for our golem to rent it 47 | //modifier checks if we already create it 48 | function createOrder( 49 | uint256 cardID, //cardID that also equals to NFT ID 50 | uint256 price, //price by day in Wei 51 | uint8 timeInDays //time in days (MAX = 63 days) 52 | ) 53 | external 54 | onlyIfCardNotAlreadyAdded(cardID) 55 | cardOnPreserve(cardID) 56 | { 57 | //checks sender equal to card owner 58 | require(msg.sender == card.cardOwner(cardID), "You not owner"); 59 | //checks if golem fed on maximum - all abilities in normal state 60 | require(card.isAllInitialAbilities(cardID) == true, "Please feed golem"); 61 | //price cant be 0 62 | require(price > 0, "Price is zero"); 63 | //time in days cant be 0 64 | require((timeInDays >= 7) && (timeInDays <= 30), "Time is 7 - 30 days"); 65 | //adding to our items 66 | marketItems.push(CardForRent( 67 | _items.current(), 68 | cardID, //cardID 69 | address(0), //cardRenter, 70 | timeInDays, //timeToRent, 71 | false, //rented, 72 | false, //closed 73 | 0, //timeWhenRentStarted, 74 | price, //priceByDay, 75 | 0, //deposit 76 | 0 //feedPrice - here because we can change feed price in one moment so fedDeposit would be change 77 | )); 78 | //listed items +1 79 | _itemsListedForRent.increment(); 80 | //all items +1 81 | _items.increment(); 82 | emit CreateOrder(cardID, price, timeInDays); 83 | } 84 | 85 | //changing market order time 86 | //first modifier says - this can do only card owner 87 | //second checks if item exist and not rented 88 | function changeOrderTimeToRent( 89 | uint256 itemID, //ID of created order 90 | uint8 _newTimeToRent //new time in days (MAX = 63) 91 | ) 92 | public 93 | onlyCardOwner(itemID) 94 | itemClosedNorRented(itemID) 95 | { 96 | marketItems[itemID].timeToRent = _newTimeToRent; 97 | emit ChangeOrderTime(itemID, _newTimeToRent); 98 | } 99 | 100 | function changeOrderPrice( 101 | uint256 itemID, //ID of created order 102 | uint256 _price //new price by day in Wei 103 | ) 104 | public 105 | onlyCardOwner(itemID) 106 | itemClosedNorRented(itemID) 107 | { 108 | marketItems[itemID].priceByDay = _price; 109 | emit ChangeOrderPrice(itemID, _price); 110 | } 111 | 112 | //closing market item 113 | //from this time it wouldnt be seen on the market 114 | //this must be called before transfer 115 | //modifier checks sender equal to card owner 116 | function closeOrder( 117 | uint256 itemID 118 | ) public onlyCardOwner(itemID) { 119 | //it should be ended by owner and withdraw 120 | require(isRented(itemID) == false, "Please end rent"); 121 | //writing data 122 | _itemsClosedRent.increment(); 123 | _itemsListedForRent.decrement(); 124 | marketItems[itemID].closed = true; 125 | emit CloseOrder(itemID); 126 | } 127 | 128 | //reopen closed order 129 | function openOrder( 130 | uint256 itemID 131 | ) public onlyCardOwner(itemID) cardOnPreserve(marketItems[itemID].cardID) { 132 | //it should be closed 133 | require(marketItems[itemID].closed == true, "Its already open"); 134 | //writing data 135 | _itemsClosedRent.decrement(); 136 | _itemsListedForRent.increment(); 137 | marketItems[itemID].closed = false; 138 | emit OpenOrder(itemID); 139 | } 140 | 141 | //calls by card owner 142 | //if rent ended we transfer DBT to card owner 143 | //also it counts fed deposit what and who 144 | function endRentAndWithdrawOwner( 145 | uint256 itemID 146 | ) public onlyCardOwner(itemID) { 147 | //checks rent is ended 148 | require(isRentEnded(itemID) == true, "Rent not ended"); 149 | _endRentAndWithdraw(itemID); 150 | emit EndRent(itemID); 151 | } 152 | 153 | //calls by renter for take back feed deposit 154 | function endRentAndWithdrawFeedDepositRenter( 155 | uint256 itemID 156 | ) public { 157 | require(isRentEnded(itemID) == true, "Rent not ended"); 158 | require(msg.sender == marketItems[itemID].cardRenter, "You not renter"); 159 | _endRentAndWithdraw(itemID); 160 | emit EndRent(itemID); 161 | } 162 | 163 | function _endRentAndWithdraw(uint256 _itemID) private { 164 | //it rented == true 165 | //means that if renter/owner call it before it call owner/renter 166 | //owner/renter cant calls it again 167 | require(isRented(_itemID) == true, "Already ended"); 168 | //sending earned DBT to owner 169 | DBT.transfer(_getCardOwnerByItemID(_itemID), marketItems[_itemID].deposit); 170 | //operating fed deposit 171 | takeFedDeposit(_itemID); 172 | //writing data 173 | marketItems[_itemID].cardRenter = address(0); 174 | marketItems[_itemID].timeWhenRentStarted = 0; 175 | marketItems[_itemID].rented = false; 176 | marketItems[_itemID].deposit = 0; 177 | _itemsRented.decrement(); 178 | } 179 | 180 | //calls by endRent 181 | //take fed deposit and transfer it to owner or renter 182 | //direction depends from changing max ability 183 | function takeFedDeposit(uint256 _itemID) private { 184 | //if golems abilities not in normal state (means not in initial) 185 | if (card.isAllInitialAbilities(marketItems[_itemID].cardID) == false) { 186 | //counting difference between initial value and actual of max ability 187 | //that means we should to fed golem to get this max ability 188 | uint16 _diff = card.diffrenceBetweenInitialAndActualMaxAbilities(marketItems[_itemID].cardID); 189 | //transfer to card owner fed deposit difference multiplyed by feeding price from fed deposit 190 | DBT.transfer(_getCardOwnerByItemID(_itemID), marketItems[_itemID].feedPrice * _diff); 191 | //changing fed deposit with considering difference 192 | itemIDToFeedDeposit[_itemID] = itemIDToFeedDeposit[_itemID] - marketItems[_itemID].feedPrice * _diff; 193 | //if fed deposit not 0 we send it to renter 194 | //otherwise will be revert 195 | if (itemIDToFeedDeposit[_itemID] != 0) { 196 | DBT.transfer(marketItems[_itemID].cardRenter, itemIDToFeedDeposit[_itemID]); 197 | } 198 | } else { 199 | //all fed deposit sended to renter 200 | DBT.transfer(marketItems[_itemID].cardRenter, itemIDToFeedDeposit[_itemID]); 201 | } 202 | } 203 | 204 | //rent order 205 | //here contract take commision 0,3% from deposit 206 | //modifier checks if item exist and not rented 207 | function rentOrder( 208 | uint256 itemID 209 | ) 210 | external 211 | itemClosedNorRented(itemID) 212 | { 213 | //counting amount to pay for rent priceByDay multiplied by timeToRent(days) 214 | uint256 amount = marketItems[itemID].priceByDay * marketItems[itemID].timeToRent; 215 | //counting fed deposit 216 | uint256 fedDeposit = countFedDeposit(itemID); 217 | //checks if user has this amount + fed deposit on balance and allowance to us 218 | require(DBT.balanceOf(msg.sender) >= amount + fedDeposit, "Balance too small"); 219 | require(DBT.allowance(msg.sender, address(this)) >= amount + fedDeposit, "Contract cant use your money"); 220 | //writing data 221 | _itemsRented.increment(); 222 | marketItems[itemID].cardRenter = msg.sender; 223 | marketItems[itemID].rented = true; 224 | marketItems[itemID].timeWhenRentStarted = block.timestamp; 225 | marketItems[itemID].feedPrice = conservation.getFeedingPrice();//fix feed price 226 | //transfer to dbt to contract 227 | DBT.transferFrom(msg.sender, address(this), amount + fedDeposit); 228 | //deposit(earned money by card owner) = 99,7% of amount because contract take commission 0,3% 229 | marketItems[itemID].deposit = amount * 997 / 1000; 230 | //contract balance + 0,3% of amount 231 | thisDBTBalance = amount - marketItems[itemID].deposit; 232 | //write fed deposit 233 | itemIDToFeedDeposit[itemID] = fedDeposit; 234 | emit Rent(itemID, msg.sender); 235 | } 236 | 237 | //counting fed deposit for rent order 238 | function countFedDeposit(uint256 itemID) private view returns(uint256) { 239 | //getting golems max ability 240 | (uint16 maxAbility,) = card.getMaxAbility(marketItems[itemID].cardID); 241 | //max ability multiply by feeding price 242 | uint256 fedDeposit = conservation.getFeedingPrice() * maxAbility; 243 | return fedDeposit; 244 | } 245 | 246 | function _getCardOwnerByItemID(uint256 _itemID) private view returns(address) { 247 | return card.cardOwner(marketItems[_itemID].cardID); 248 | } 249 | 250 | function getUserDeposit(uint256 itemID) public view onlyCardOwner(itemID) returns(uint256) { 251 | return marketItems[itemID].deposit; 252 | } 253 | 254 | function isRentEnded(uint256 itemID) public view returns(bool) { 255 | return block.timestamp > 256 | marketItems[itemID].timeWhenRentStarted + (marketItems[itemID].timeToRent * secondsInADay); 257 | } 258 | 259 | function isRented(uint256 itemID) public view returns(bool) { 260 | return marketItems[itemID].rented; 261 | } 262 | 263 | function isClosed(uint256 itemID) public view returns(bool) { 264 | return marketItems[itemID].closed; 265 | } 266 | 267 | function getUserRenter(uint256 itemID) public view returns(address) { 268 | if (isRented(itemID) == false) { 269 | return address(0); 270 | } 271 | if (isRentEnded(itemID) == true) { 272 | 273 | return address(0); 274 | } 275 | return marketItems[itemID].cardRenter; 276 | } 277 | 278 | function getItemIDByCardID(uint256 cardID) public view returns(uint256, bool) { 279 | uint256 _itemID; 280 | bool exist; 281 | for (uint256 i = 0; i < marketItems.length; i++) { 282 | if (marketItems[i].cardID == cardID) { 283 | _itemID = marketItems[i].itemID; 284 | exist = true; 285 | } 286 | } 287 | return (_itemID, exist); 288 | } 289 | 290 | //FETCHING BLOCK 291 | function fetchCardOnMarket() public view returns (CardForRent[] memory) { 292 | CardForRent[] memory listed = new CardForRent[](_itemsListedForRent.current()); 293 | for (uint256 i = 0; i < marketItems.length; i++) { 294 | if ((marketItems[i].rented == false) && (marketItems[i].closed == false)){ 295 | listed[i] = marketItems[i]; 296 | } 297 | } 298 | return listed; 299 | } 300 | 301 | function fetchUserCardOnMarket(address user) public view returns (CardForRent[] memory) { 302 | CardForRent[] memory userListed = new CardForRent[](card.cardCount(user)); 303 | for (uint256 i = 0; i < marketItems.length; i++) { 304 | if (_getCardOwnerByItemID(i) == user){ 305 | userListed[i] = marketItems[i]; 306 | } 307 | } 308 | return userListed; 309 | } 310 | 311 | function fetchRenterCardOnMarket(address renter) public view returns (CardForRent[] memory) { 312 | CardForRent[] memory rented = new CardForRent[](_itemsRented.current()); 313 | for (uint256 i = 0; i < marketItems.length; i++) { 314 | if (marketItems[i].cardRenter == renter) { 315 | rented[i] = marketItems[i]; 316 | } 317 | } 318 | return rented; 319 | } 320 | 321 | //OWNER FUNCTIONS 322 | function withdrawDBTOwner() public isOwner { 323 | DBT.transfer(msg.sender, thisDBTBalance); 324 | } 325 | 326 | function getThisDBTBalance() public view isOwner returns(uint256) { 327 | return thisDBTBalance; 328 | } 329 | 330 | function setDBT(address _dbt) public isOwner { 331 | DBT = IBEP20(_dbt); 332 | } 333 | 334 | function setCard(address _card) public isOwner { 335 | card = ICard(_card); 336 | } 337 | 338 | function setConserve(address _conserve) public isOwner { 339 | conservation = IConservation(_conserve); 340 | } 341 | 342 | //TESTING 343 | function mockTime(uint256 itemID, uint256 _newTime) public isOwner { 344 | marketItems[itemID].timeWhenRentStarted = _newTime; 345 | } 346 | 347 | //MODIFIERS 348 | modifier onlyIfCardNotAlreadyAdded(uint256 _cardID) { 349 | for (uint256 i = 0; i < _items.current(); i++){ 350 | require(marketItems[i].cardID != _cardID, "Already exist"); 351 | } 352 | _; 353 | } 354 | 355 | modifier onlyCardOwner(uint256 _itemID) { 356 | require(msg.sender == _getCardOwnerByItemID(_itemID), "You not owner of card"); 357 | _; 358 | } 359 | 360 | modifier itemClosedNorRented(uint256 _itemID) { 361 | require(marketItems[_itemID].rented == false, "Item already rented"); 362 | require(marketItems[_itemID].closed == false, "Item closed"); 363 | _; 364 | } 365 | 366 | modifier cardOnPreserve(uint256 cardID) { 367 | require(conservation.isPreservated(cardID) == false, "Card on preserve"); 368 | _; 369 | } 370 | 371 | } 372 | --------------------------------------------------------------------------------