Hello fellow Web3 enthusiasts, I'm 0xHunter008, a Pentester and Smart Contract Auditor. I am actively participating in Bug Bounties on Immunefi. I’m on my journey through the exciting world of blockchain security, I've encountered various challenges, particularly in decentralized finance (DeFi) project audits. One platform that provides an excellent learning ground for security professionals is Damn Vulnerable DeFi (DVDF) CTF.
To share my insights and contribute to the community, I've decided to launch a series of Damn Vulnerable DeFi challenges Solutions. These challenges are carefully crafted to simulate real-world vulnerabilities, making them an excellent playground for honing your skills as a smart contract auditor. Without weasting any time let’s start.

Here is the repository of Damn Vulnerable Defi Solutions:
https://github.com/0xHunter008/Foundry-Damn-Vulnerable-Defi-Solution
Welcome to the Unstoppable challenge in the Damn Vulnerable DeFi (DVDF) CTF series. Your mission is to disrupt the UnstoppableLender contract's free flash loan offerings. The contract currently holds 1 million DVT tokens in its vault and allows users to borrow tokens without collateral during a grace period. The primary goal for this challenge is to strategically exploit the vulnerabilities within the UnstoppableLender contract and prevent it from executing any flash loans. With 100 DVT tokens in your arsenal
The UnstoppableLender contract represents a lending protocol where users can deposit tokens and take flash loans. Flash loans allow users to borrow tokens without collateral as long as the borrowed amount is returned within the same transaction. Let's examine the key components of the contract:
function depositTokens(uint256 amount) external nonReentrant {
if (amount == 0) revert MustDepositOneTokenMinimum();
damnValuableToken.transferFrom(msg.sender, address(this), amount);
poolBalance = poolBalance + amount;
}
Users can deposit tokens into the lending pool using the depositTokens function. The poolBalance is updated accordingly.
function flashLoan(uint256 borrowAmount) external nonReentrant {
if (borrowAmount == 0) revert MustBorrowOneTokenMinimum();
uint256 balanceBefore = damnValuableToken.balanceOf(address(this));
if (balanceBefore < borrowAmount) revert NotEnoughTokensInPool();
if (poolBalance != balanceBefore) revert AssertionViolated();
damnValuableToken.transfer(msg.sender, borrowAmount); IReceiver(msg.sender).receiveTokens(address(damnValuableToken),borrowAmoun;
uint256 balanceAfter = damnValuableToken.balanceOf(address(this));
if (balanceAfter < balanceBefore) revert FlashLoanHasNotBeenPaidBack();
}
The flashLoan function plays a critical role in the vulnerability. Here's a detailed breakdown:
Balance Verification:
balanceBefore: The function checks if the contract's token balance is sufficient for the requested flash loan amount.
Equality Check:
if (poolBalance != balanceBefore) revert AssertionViolated();: This is the heart of the vulnerability. The function ensures that the poolBalance is strictly equal to the balanceBefore the flash loan. If not, it triggers theAssertionViolatederror.
Token Transfer:
damnValuableToken.transfer(msg.sender, borrowAmount);: If all conditions pass, it transfers the requested tokens to the borrower.
Receiver Callback:
IReceiver(msg.sender).receiveTokens(address(damnValuableToken), borrowAmount);: Calls thereceiveTokensfunction in the receiver contract.
Repayment Check:
Ensures that the flash loan is paid back by comparing the balance before and after the token transfer.
The key to this exploit lies in the flashLoan function within the UnstoppableLender contract. The vulnerability stems from the strict equality check in the line: if (poolBalance != balanceBefore) revert AssertionViolated();. Exploiting this check will be the key to achieving our goal.
The Exploit Strategy :
Direct Token Transfer:
Exploiters directly call the
transferfunction of the ERC-20 DVT token to send tokens to the lending pool contract.
The Exploit in Action :
function testExploit() public {
vm.startPrank(attacker);
// Sending tokens to UnstoppableLender to trigger poolBalance != balanceBefore during executeFlashLoan
dvt.transfer(address(unstoppableLender), 1);
vm.stopPrank();
vm.expectRevert(UnstoppableLender.AssertionViolated.selector);
validation();
console.log(unicode"\n🎉 Congratulations, you can go to the next level!🎉");
}
This function is the heart of our exploit. This Exploite is written in a foundry test case. We start a prank to manipulate the token balance during a flash loan, triggering the assertion violation. If successful, the exploit prevents further flash loans from the UnstoppableLender contract.
Start Prank:
vm.startPrank(attacker);: Initiates the prank by setting up the attacker's environment for manipulation.
Token Transfer:
dvt.transfer(address(unstoppableLender), 1);: Sends 1 DVT token directly to the UnstoppableLender contract. This triggers the vulnerability by makingpoolBalance != balanceBeforeduring theexecuteFlashLoanfunction.
Stop Prank:
vm.stopPrank();: Concludes the prank, finalizing the manipulation.
Expect Revert:
vm.expectRevert(UnstoppableLender.AssertionViolated.selector);: Checks if the expectedAssertionViolatederror is triggered. This error indicates the success of the exploit.
Validation Function:
validation();: Calls the validation function to confirm that it is no longer possible to execute flash loans after the exploit.
Congratulations Message:
console.log(unicode"\n🎉 Congratulations, you can go to the next level! 🎉");: If the exploit was successful, a congratulatory message is displayed, signaling that the flash loan offerings have been effectively halted.
function validation() internal {
// It is no longer possible to execute flash loans
vm.startPrank(someUser);
receiverUnstoppable.executeFlashLoan(10);
vm.stopPrank();
}
A validation function is provided to confirm that the vulnerability has been addressed After the exploit, an attempt is made to execute a flash loan, and the testing framework expects no errors, validating the fix.
The exploit's success in the Unstoppable challenge underscores the importance of scrutinizing assumptions made during smart contract development. In this case, the strict equality check (assert(poolBalance == balanceBefore)) became the Achilles' heel, enabling the attacker to manipulate the contract and execute a Distributed Denial of Service (DDoS) attack. Instead of using strict equality checks (==), opt for greater than or equal checks (>=) when comparing values. This practice helps accommodate potential changes in a contract's balance during function execution.
If you have made it to this far then thanks for reading my Article. Let’s connect on
0xHunter008. No need to buy me coffee or donate crypto. Teach me something here 0xHunter008.
