EP.2 - Practical Flash loan on Decentralized Finance (DeFi)

thispost
5 min readAug 14, 2021

--

Intro

In EP.1, we’ve known how to trigger the Flash loan on the pair contract. In this EP., we’ll simulate the Flash loan by using arbitrage as a use case. Why just simulation? for simplicity’s sake, a profitable arbitrage can’t be always conducted, it depends on the different prices on 2 exchanges at a time. In a realistic case, the off-chain script will be used to monitor the price and if there are opportunities, it calls the smart contract to do further.

Prepare the simulation

Create the testing tokens

On BSC Testnet, the token can be created freely, even WBNB can be a different address on 2 exchanges. For simplicity’s sake, we’ll create the T-WBNB (WBNB) as follows:

pragma solidity ^0.6.0;import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.1.0/contracts/token/ERC20/ERC20.sol";contract WBNB is ERC20 {
constructor () public ERC20("Test WBNB", "T-WBNB") {
_mint(msg.sender, 1000000 * (10 ** uint256(decimals())));
}
}

and T-BUSD (BUSD) as follows:

pragma solidity ^0.6.0;import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.1.0/contracts/token/ERC20/ERC20.sol";contract BUSD is ERC20 {
constructor () public ERC20("Test BUSD", "T-BUSD") {
_mint(msg.sender, 1000000 * (10 ** uint256(decimals())));
}
}

Note our T-BUSD and T-WBNB addresses for further usage.

T-WBNB: 0xbA4756Ef6f19E75EAfF9e20Ed69d913C7eec4176
T-BUSD: 0xCb096FA97d970ac01857cB76186e228262dE2999

Add liquidity to 2 exchanges on Testnet i.e., PancakeSwap and BakerySwap (Source).
We intentionally create the different prices in 2 pairs as shown in Listing 1.
- On PancakeSwap, 100 T-BUSD/T-WBNB.
- On BakerySwap, 50 T-BUSD/T-WBNB.
This can simulate an arbitrage opportunity to loan T-BUSD from PancakeSwap and sell on BakerySwap.

Listing 1 - Liquidity adding on both PancakeSwap and BakerySwap.

We now have the pair address on 2 platforms, note it for further usage.

Pancake LPs (T-WBNB/T-BUSD): 0x0126D4Bbb6f6059128869E49B25F6dA9121D8C75
Bakery LPs (T-WBNB/T-BUSD): 0x276C8329a6Ca4ad36956B61660acD900ec0aff56

Create the skeleton code

Initial the relevant addresses and interfaces.
Openzeppelin’s SafeMath -> ‘./Libraries/SafeMath.sol’
IPancakeERC20 -> ‘./Interfaces/IPancakeERC20.sol’
IPancakeFactory -> ‘./Interfaces/IPancakeFactory.sol’
IPancakePair -> ‘./Interfaces/IPancakePair.sol’
IPancakeRouter01 -> ‘./Interfaces/IPancakeRouter01.sol’
PancakeLibrary -> ‘./Libraries/PancakeLibrary.sol’
IBakerySwapRouter -> ‘./Interfaces/IBakerySwapRouter.sol’

pragma solidity ^0.6.0;import './Libraries/SafeMath.sol';
import './Interfaces/IPancakeERC20.sol';
import './Interfaces/IPancakeFactory.sol';
import './Interfaces/IPancakePair.sol';
import './Interfaces/IPancakeRouter01.sol';
import './Libraries/PancakeLibrary.sol';
import './Interfaces/IBakerySwapRouter.sol';
contract FlashLoanArbitrage{
address owner;

//token
address wbnbAddr;
address busdAddr;

//Pancake
address wbnb_busd_pancakeLPAddr;
address pancakeRouterAddr;
address pancakeFactoryAddr;
IPancakeRouter01 pancakeRouter;

//Bakery
address wbnb_busd_bakeryLPAddr;
address bakeryRouterAddr;
address bakeryFactoryAddr;
constructor() public {
//me
owner = msg.sender;

//Bakery
bakeryRouterAddr = 0xCDe540d7eAFE93aC5fE6233Bee57E1270D3E330F;
bakeryFactoryAddr = 0x01bF7C66c6BD861915CdaaE475042d3c4BaE16A7;

//Pancake
wbnb_busd_pancakeLPAddr = 0x0126D4Bbb6f6059128869E49B25F6dA9121D8C75;
pancakeRouterAddr = 0x9Ac64Cc6e4415144C455BD8E4837Fea55603e5c3;
pancakeFactoryAddr = 0xB7926C0430Afb07AA7DEfDE6DA862aE0Bde767bc;

//token
busdAddr = 0xCb096FA97d970ac01857cB76186e228262dE2999;
wbnbAddr = 0xbA4756Ef6f19E75EAfF9e20Ed69d913C7eec4176;
}
}

Triggering the Flash loan on Pancake LPs

As a simulation, the price of T-WBNB/T-BUSD on PancakeSwap is cheaper than BakerySwap, we’ll loan on the PancakeLPs (pair). To trigger the Flash loan, we’ll create a function that calls swap() on PancakeLPs and specify the not-null bytes for the 4th argument.

function pancakeFlashLoan(
uint amount0,
uint amount1
) external {
IPancakePair(wbnb_busd_pancakeLPAddr).swap(
amount0,
amount1,
address(this),
bytes("something")
);
}

Create a function to do with the funds

As mentioned on EP.1, PancakeLPs has the callee name pancakeCall(). So, we’ll have this function on our contract.
Prepare pancakeCall() skeleton code on our smart contract.

function pancakeCall(
address _sender,
uint _amount0,
uint _amount1,
bytes calldata _data
) external {
}

At this point, we already loan the fund from PancakeLPs. In this case, 200 T-BUSD is loaned and will swap to T-WBNB at BakerySwap,
1: Which token0 or token1 is loaned, in this case, token0 is T-WBNB and token1 is T-BUSD.
2 - 4: Prepare the path to swap from T-BUSD to T-WBNB.
6: Calculate the minimum amount we are expecting to obtain T-WBNB exchanged by T-BUSD.
7: Deadline as a UNIX timestamp for the argument of swapExactTokensForTokens().
8 - 9: Approve Bakery’s router.
10: swap T-BUSD to T-WBNB on Bakery’s router.

1:   uint amountToken = _amount0 == 0 ? _amount1 : _amount0;
2: address[] memory path = new address[](2);
3: path[0] = busdAddr;
4: path[1] = wbnbAddr;
5:
6: uint amountOutMin =
IBakerySwapRouter(bakeryRouterAddr).getAmountsOut(
amountToken,
path
)[1];
7: uint deadline = now + 20 minutes;
8: IPancakeERC20 busd = IPancakeERC20(busdAddr);
9: busd.approve(address(bakeryRouterAddr), amountToken);

10: uint amountReceived =
IBakerySwapRouter(bakeryRouterAddr).swapExactTokensForTokens(
amountToken,
amountOutMin,
path,
address(this),
deadline
)[1];

Repayment strategies (on simulation)

We can repay either T-WBNB or T-BUSD if we perform the Flash loan from “T-WBNB/T-BUSD” pair. There are the following strategies:

Loan T-BUSD, repay T-WBNB (we’ll use this)

1. Loan a T-BUSD from the PancakeLPs “T-WBNB/T-BUSD”.
2. Swap T-BUSD to a T-WBNB at BakerySwap.
3. Repay a T-WBNB to the PancakeLPs “T-WBNB/T-BUSD”.

Loan T-BUSD, repay T-BUSD

1. Loan a T-BUSD from the PancakeLPs “T-WBNB/T-BUSD”.
2. Immediately repay T-BUSD to the PancakeLPs “T-WBNB/T-BUSD”, we need to have an extra T-BUSD in our smart contract for an additional fee since it is unprofitable.

Implementing the code that Loan T-BUSD, repay T-WBNB

1 - 3: Prepare the path to calculate the amount of T-WBNB for repayment.
4: Calculate T-WBNB for repayment from 200 T-BUSD we’ve loaned.
5 - 6: Repay T-WBNB to PancakeLPs.
7: The profit amount after swapping from T-BUSD to T-WBNB.
8: Transfer T-WBNB (profit) to our wallet.

1:   address[] memory pathRepay = new address[](2);
2: pathRepay[0] = wbnbAddr;
3: pathRepay[1] = busdAddr;
4: uint amountRepay = PancakeLibrary.getAmountsIn(
pancakeFactoryAddr,
amountToken,
pathRepay
)[0];

5: IPancakeERC20 otherToken = IPancakeERC20(wbnbAddr);
6: otherToken.transfer(wbnb_busd_pancakeLPAddr, amountRepay);
7: uint profit =
IPancakeERC20(wbnbAddr).balanceOf(address(this));
8: otherToken.transfer(owner, profit);

Let’s try it

Assemble all code, deploy and call pancakeFlashLoan(), specifying amount0 (T-WBNB) as 0 and amount1 (T-BUSD) as 200 (200000000000000000000 in Wei, since it is uint256).

Listing 2 - Perform the simulation of Flash loan and arbitrage.

Let’s see the token transferred in Tx.
1. Perform the Flash loan for 200 T-BUSD on PancakeLPs. Now, our smart contract receives 200 T-BUSD.
2 - 3. Swap 200 T-BUSD for ~3.91 T-WBNB on BakerySwap, we have an extra ~1.91 T-WBNB because we can only receive ~2 T-WBNB on PancakeSwap. That is arbitrage we’ve simulated.
4. Repay ~2 T-WBNB plus an additional fee ~0.04 T-WBNB to PancakeLPs.
5. The profit ~1.86 T-WBNB is transferred to our wallet.
We simulate a profitable arbitrage without having our own token.

Listing 3 - Token transferred during the Flash loan and arbitrage.

Outro

In a realistic case, the off-chain script is required to monitor and find an arbitrage opportunity, if it is found, pancakeFlashLoan() (for this smart contract) is called. For an enhancement, we should calculate the loan amount, swap fee, etc., to make sure our strategy is profitable. For the attack, there is a multiple pools (pair) Flash loan to loan a large amount of token. In EP.3, I’ll try to give an example of the Pancake Bunny exploit in the part that the attacker performed the Flash loan for WBNB on the multiple pools.

--

--