Challenge 3

Challenge to solve

In order to get the flag in challenge 3, we needed to make the current balance of the FlashLender object to 0. The only way to decrease the balance was to call the loan() function from another module.

Solution to the challenge

The solution to this challenge was quite simple, as all we had to do was create a module that allowed us to call the loan function. We were able to take out a loan with the full amount in the object, and then capture the flag since the balance was 0 at the moment.

Explanation of the challenge and solution

You may have noticed that we repaid the loan after we captured the flag. This was not from generosity as I am sure most people would prefer to keep the 1000 coins that they were able to borrow with no interest (I know I would). The loan function returns two things, the coins that were being borrowed and the receipt for the loan. The receipt is a struct that is created in the loan function. It is important to note that the receipt struct does not have any of the abilities that can be given to structs.

Challenge 3 module
struct Receipt {
    flash_lender_id: ID,
    amount: u64
}

Sui Move is built so that all structs without the drop ability that are used in a function must be "sent" somewhere before the module function call ends. Below shows the error that is thrown when we try to ignore the receipt struct with the underscore keyword.

Error thrown when trying to ignore the receipt variable

Well, maybe if we can ignore it, we can just keep it to ourselves by transferring it from the function to our account. We can use the transfer function from the Sui core module to transfer the receipt struct to our accounts so it will be considered "sent" somewhere and not dropped. The only flaw with this strategy is that the struct that is passed into the transfer function is required to have the key ability, which the receipt struct does not have either.

Error thrown when trying to transfer the receipt

It seems like the only choice we have to call another function in the module that can accepts the receipt struct. The only function that accepts the receipt struct is the check() function which checks that the receipt is associated with the given FlashLender (there can be multiple FlashLender objects deployed) and that the current balance of the Flashlender is equal to or greater than the amount that was borrowed. So, basically, the check function will fail unless we have already repaid the loan before with the repay() function.

In Sui and most blockchains, transactions are atomic, meaning that if a single part of the transaction fails, the whole transaction fails and is not added to the blockchain. The effects of the transactions are only considered once the transaction is added to the blockchain. Thus, if our call to the check function fails, the flag capture is reverted and we are back to square one.

Therefore, the only option we have after taking the loan and capturing the flag is to repay the loan and verify that the balance is all back in the FlashLender. This creates the requirement to make sure to pay back loans within the same function that took out the loan. This scheme is common in the blockchain space and is called a flash loan.

Flash loans are loans that taken out and repaid in the same transaction. Since the full loan amount is returned at the end of the transaction, there is never a risk that a borrower will not return the money so there is no reason to charge interest in the loan since they are fully guaranteed and virtually instantly repaid. Flashloans are typically borrowed at very high amounts to perform arbitrage, which is typically when two different entities are selling the same asset at slightly difference prices and the borrower uses the borrowed funds to buy and sell the asset to make a profit. The large amount of funds is directly returned but the borrower has made a profit from the differences in asset price.

Last updated