DEV Community

Cover image for Learn to execute Flash Swaps ⚡on Uniswap by yourself
a_mature_dev for UVLabs

Posted on

Learn to execute Flash Swaps ⚡on Uniswap by yourself

In the previous article, we understood how we can implement a swap between tokens using Uniswap.

In this article, we will be moving one level ahead by exploring Flash Swap by coding a flash swap contract and run tests on it!

We highly recommend you go through the previous article before.

What is a Flash Loan?

Unlike Traditional Loans, in Flash Loans, funds are borrowed and returned in one transaction. This is a MUST.

In Defi, traders (usually through bots) keep looking out for arbitrage opportunities to gain benefits by trading between platforms supplying different prices for the same asset.

This is where the Flash Loans comes into the picture (albeit usually).

With the help of Flash Loan, traders can borrow a large sum of money to execute an arbitrage trade.

How does Flash Loan Arbitrage work?

Example

Consider a situation, where Ethan buys a book for $10 from the Bookstore and then sells that book to Jennifer for $20. In this situation, Ethan buys a book using his money and then straight away doubles it by selling it to Jennifer.

This is exactly how trading arbitrage works.

But unlike Ethan, who used his own money for buying the book from the bookstore, here we could simply use Flash Loans to borrow $10 and then execute a trade, similar to selling the book and then repay the loan (yes, all in 1 single transaction).

Lets dive in coding our own Flash swap contract and test it out!! 😎

1. Create a project and install dependencies

Use the following commands on your CLI to initialize your project.

mkdir Flash_swap && cd Flash_swap
npm init -y
Enter fullscreen mode Exit fullscreen mode

Now, install the required dependencies that we’ll be using for the project.

Use the command provided below and run it on your CLI to install them.

npm install --save hardhat @nomiclabs/hardhat-ethers @nomiclabs/hardhat-waffle ethers @uniswap/v2-core dotenv hardhat chai
Enter fullscreen mode Exit fullscreen mode

2. Initialize your Hardhat Project

Run the command npx hardhat on your CLI and create an empty hardhat config file, since we’re going to build everything from scratch.

Customize your hardhat config:

Because we are going to fork the mainnet to test the Flashswap. Therefore, your hardhat config should look something similar to this:

CodeBlock

Note: Replace the <key> component of the URL with your personal Alchemy API key.

Also, if you’re new to mainnet forking, read our article based on it and then follow along with this article.

3. Write a smart contract for Flash swap

Create directories for contracts and tests for better code organisation.

Use the following code in your CLI.

mkdir contracts && mkdir tests
Enter fullscreen mode Exit fullscreen mode

In order to write the flash swap contract, create a file inside the contracts directory and name it flashswap.sol

Writing the smart contract:

First, import the interfaces required and create a contract named as flashSwap.

We will import Uniswap’s interface to use its functions. You can get the interface using this link.

We have also imported IUniswapV2Callee interface. Uniswap will call this function when we execute the flash swap. Technically this is the callback function that Uniswap will call.

It should look similar to this:

CodeBlock

Next, we create our contract flashSwap which inherits from IUniswapV2Callee. Solidity supports inheritance between smart contracts, where multiple contracts can be inherited into a single contract. The contract from which other contracts inherit features is known as a base contract, while the contract which inherits the features is called a derived contract.

This contract will have 2 functions:

  • testFlashSwap(): This is the function we will use to call to trigger the flash swap transaction.
  • uniswapV2Call(): This is the function that the Uniswap calls

CodeBlock

Writing the **testFlashSwap* function:*

This function will take 2 parameters (A) the token that is to be borrowed from Uniswap {the address| and (B) the amount we want to borrow.

  • First thing that we’ll do is, check that the pair contract for _tokenBorrow and WETH exists. We can do that by calling the getPair function on UniswapV2Factory NOTE: In Uniswap v2, all token pairs are with WETH as one of the coin and hence, to check if a particular token is available on Uniswap, we simply check its availability with WETH. More on what is WETH —> https://weth.io/
address pair = IUniswapV2Factory(UniswapV2Factory).getPair(_tokenBorrow, WETH);
require(pair != address(0), "!pair");
Enter fullscreen mode Exit fullscreen mode

{Please note, we have defined WETH as a variable in our contract already. Please refer to our entire contract at the end}

Inside the UniswapV2Pair, we have 2 tokens, token0, and token1.

We now will check if _tokenBorrow is equal to _token0 or not.

If the values are equal, then amount0Out will be the _amount argument that we pass to the function, otherwise, it would be equal to zero.

Similarly, we will check the same for _token1.

address token0 = IUniswapV2Pair(pair).token0();
address token1 = IUniswapV2Pair(pair).token1();
uint256 amount0Out = _tokenBorrow == token0 ? _amount : 0;
uint256 amount1Out = _tokenBorrow == token1 ? _amount : 0;
Enter fullscreen mode Exit fullscreen mode

As a result, either you’ll have amount0Out equal to _amount and amount1Out equal to 0 or vice-versa.

Then we pass these amount values in Uniswap’s swap function.

bytes memory data = abi.encode(_tokenBorrow, _amount);
IUniswapV2Pair(pair).swap(amount0Out, amount1Out, address(this), data);
Enter fullscreen mode Exit fullscreen mode

As you’ll notice, this is the exact same function that we call to perform a simple swap on Uniswap. Refer to our previous article over here.

The only difference is the last input. If it is empty, then Uniswap will try a simple swap execution.

If it is not empty, that is it has any data, then it would trigger a flashswap.

To pass in the input, we will encode the tokenBorrow and amount as bytes and then pass it to the swap function.

The testFlashSwap function should look similar to:

CodeBlock

Let’s write the **uniswapV2Call:* 🤩*

  • Basic housekeeping: Right now, anyone can access this function, therefore the first thing that we’re going to do is check that this function can only be called by the pair contract. And then, we’ll also check that the sender is equal to the pair contract. For that, use the following code:
address token0 = IUniswapV2Pair(msg.sender).token0();
address token1 = IUniswapV2Pair(msg.sender).token1();
// call uniswapv2factory to getpair 
address pair = IUniswapV2Factory(UniswapV2Factory).getPair(token0, token1);
require(msg.sender == pair, "!pair");
Enter fullscreen mode Exit fullscreen mode
  • Decoding data: Then, we will decode the data that is passed to us by Uniswap, this is not a mandatory step, but encouraged to do so.
(address tokenBorrow, uint amount) = abi.decode(_data, (address, uint));
Enter fullscreen mode Exit fullscreen mode
  • Computing fee: Uniswap charges 0.3% for any form of swap. Using the following code, we are computing the fee that our contract will have to bear for undertaking the flashswap.
uint fee = ((amount * 3) / 997) + 1;
uint amountToRepay = amount + fee;
Enter fullscreen mode Exit fullscreen mode
  • Repayment: Lastly, we have to pay Uniswap the borrowed token including the fee. It can be achieved by using the following code.
IERC20(tokenBorrow).transfer(pair, amountToRepay);
Enter fullscreen mode Exit fullscreen mode

That completes our uniswapV2Call function, which would look similar to the following image:

CodeBlock

Now, our flashSwap contract is complete, and should be similar to this:

CodeBlock

4. Let’s write some Test Script for our Contract

First of all, we will import the necessary libraries, ERC20 abi, and will also create a basic structure of our test script.

Use the following reference to get a better understanding:

CodeBlock

  • Now, we will define the address of the contract which we’re going to impersonate and the USDC address.

We will also define the amount that we’re going to borrow.

Use the following:

const USDCHolder = "0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE";
const USDCAddress = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
const borrowAmount = 1000000000; // since USDC is 6 decimals, this corresponds to 1000 USDC Coins
Enter fullscreen mode Exit fullscreen mode
  • Next, we will begin with the before block use the contract we created and deploy it.
before(async () => {
        const TestFlashSwapFactory = await ethers.getContractFactory("flashSwap");
        TestFlashSwapContract = await TestFlashSwapFactory.deploy();
        await TestFlashSwapContract.deployed();
});
Enter fullscreen mode Exit fullscreen mode
  • Finally, we write our test script to check our Flash swap execution.

A: First, we will impersonate the account which we want to use:

await hre.network.provider.request({
            method: "hardhat_impersonateAccount",
            params: [USDCHolder],
});
const impersonateSigner = await ethers.getSigner(USDCHolder);
Enter fullscreen mode Exit fullscreen mode

B: Then, we will define the USDC Contract

const USDCContract = new ethers.Contract(USDCAddress, ERC20ABI, impersonateSigner)
Enter fullscreen mode Exit fullscreen mode

C: We already knew that Uniswap charges a fee to implement a Flash Swap. Therefore, we will calculate the value of the fee.

const fee = Math.round(((borrowAmount * 3) / 997)) + 1;
Enter fullscreen mode Exit fullscreen mode

D: We need to understand that, since our contract just got deployed in the before block, it does not have any Ether or any USDC. Then, once the flash loan is implemented, the contract has to return the borrowed amount plus the fee.

Therefore, we have to transfer the amount of the fee from the impersonated account to our contract, in order to complete the trade.

Therefore, we will use the following command to transfer the fee to the contract:

await USDCContract.connect(impersonateSigner).transfer(TestFlashSwapContract.address, fee)
Enter fullscreen mode Exit fullscreen mode

D: Then, we will call the testFlashSwap function which we have defined in our contract.

await TestFlashSwapContract.testFlashSwap(USDCContract.address, borrowAmount)
Enter fullscreen mode Exit fullscreen mode

E: It’s time to check whether the Flashswap executed was correct or not. For that, we will check whether the balance of our contract, after the flashswap and the payment of the exact fee that we computed is 0, as it should be, or not.

const TestFlashSwapContractBalance = await USDCContract.balanceOf(TestFlashSwapContract.address)
expect(TestFlashSwapContractBalance.eq(BigNumber.from("0"))).to.be.true;
Enter fullscreen mode Exit fullscreen mode

Your final test script should look like this:

CodeBlock

Finally, run your test in your CLI using the command npx hardhat test tests/flashswaptest

The result should look like this:

CodeBlock

Voila 🥳🥳🥳 ****Our flashswap passed with flying colours 🤣

Summary

Flash swap allows you to borrow any ERC20 token on Uniswap and execute any code logic, as long as you repay the same token or any other token of the same value plus the fee in the same transaction.

Hope you like our efforts and try and run this by yourselfs. :)

Of course! If you get any error, you can ask us by tagging @uv_labs on Twitter.

Again, all the code that we just ran through is over here 👉 Github repository. Do give us a star if you liked our work.

Discussion (0)