Objectives
There’s a pool with 1000 ETH in balance, offering flash loans. It has a fixed fee of 1 ETH.
A user has deployed a contract with 10 ETH in balance. It’s capable of interacting with the pool and receiving flash loans of ETH.
Our objective is to empty the user's contract and drain all the ETH, and bonus points if we can do it in a single transaction.
Smart Contract Analysis
NaiveReceiverLenderPool.sol
FlashLoanReceiver.sol
The Exploitation
The vulnerability lies in the flashLoan() function inside the lender pool. There's no validation happening inside the function to make sure that the borrower is msg.sender. Without this, any external attacker would be able to enter any user's address as the borrower and request a flash loan for them.
If the function is called once, i.e., flashLoan(FlashLoanReceiver_address, 0) it'll drain 1 Ether fee from the Receiver. To drain all the balance, we must call this function 10 times. This can be achieved by using the following code in the test case:
But this is not efficient, neither is this in a single transaction. To do this in one transaction, we can make use of loops. Here's a better solution:
This calls the flashLoan() function 10 times with 0 borrow amount, which charges a fee of 1 Ether in every flash loan call, eventually draining the flash loan receiver.
Run the script using yarn run naive-receiver and the test case will pass.

Key Takeaways
Input validation and access control should be the utmost priority when implementing any sensitive function dealing with tokens or Ether. The attack would have been prevented had there been a validation on the borrower's address like require(borrower == msg.sender);.