Long and Short

Intro

Lending can be seen as leverage. For example, I predict ETH price is going up in the near future. I supply 10 ETH as collateral to Compound and borrow 300 DAI. I would buy more ETH using this 300 DAI I just borrowed, so that when ETH price indeed goes up my asset total value goes up even more. I will buy 300 DAI with my ETH balance and repay the loan, and I still have more ETH at hand because ETH price went up. These extra ETH is my profit. This is called "long ETH".

"Short ETH" is just the other way around. If we expect ETH price to drop, we can deposit DAI as collateral and borrow ETH. Then we sell these borrowed ETH and buy DAI. When the price of ETH does go down, we sell DAI and buy ETH, then pay back the loan. We will be left some DAI and that is our profit.

We will be writing functions to long ETH. It takes the following steps:

  1. supply ETH

  2. borrow stable coin (DAI, USDC)

  3. buy ETH on Uniswap

When the price of ETH goes up:

  1. sell ETH on Uniswap

  2. repay borrowed stable coin

Code:

Setup

Nothing special here:

CEth public cEth;
CErc20 public cTokenBorrow;
IERC20 public tokenBorrow;
uint public decimals;

Comptroller public comptroller = Comptroller(0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B);
PriceFeed public priceFeed = PriceFeed(0x922018674c12a7F0D394ebEEf9B58F186CdE13c1);

IUniswapV2Router private constant UNI = IUniswapV2Router(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
IERC20 private constant WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);

constructor(
    address _cEth,
    address _cTokenBorrow,
    address _tokenBorrow,
    uint _decimals
) {
    cEth = CEth(_cEth);
    cTokenBorrow = CErc20(_cTokenBorrow);
    tokenBorrow = IERC20(_tokenBorrow);
    decimals = _decimals;

    // enter market to enable borrow
    // we are going to supply ETH (cEth)
    address[] memory cTokens = new address[](1);
    cTokens[0] = address(cEth);
    uint[] memory errors = comptroller.enterMarkets(cTokens);
    require(errors[0] == 0, "Comptroller.enterMarkets failed.");
}

receive() external payable {}

Step 1: supply ETH

We deposit ETH into Compound as collateral, which is equivalent to minting cEth:

function supply() external payable {
    cEth.mint{value: msg.value}();
}

Step 2 and 3: borrow stable coin (DAI, USDC) and buy ETH on Uniswap

Compute how much DAI we can borrow:

function getMaxBorrow() external view returns (uint) {
    // This is the amount we can borrow in USD
    (uint error, uint liquidity, uint shortfall) = comptroller.getAccountLiquidity(
        address(this)
    );

    require(error == 0, "error");
    require(shortfall == 0, "shortfall > 0");
    require(liquidity > 0, "liquidity = 0");

    // Price is also in USD
    uint price = priceFeed.getUnderlyingPrice(address(cTokenBorrow));
    uint maxBorrow = (liquidity * (10**decimals)) / price;

    return maxBorrow;
}

Borrow DAI and swap DAI for WETH in Uniswap:

function long(uint _borrowAmount) external {
    // borrow DAI
    require(cTokenBorrow.borrow(_borrowAmount) == 0, "borrow failed");
    
    // buy WETH on Uniswap
    uint bal = tokenBorrow.balanceOf(address(this));
    tokenBorrow.approve(address(UNI), bal);
    address[] memory path = new address[](2);
    path[0] = address(tokenBorrow);
    path[1] = address(WETH);
    UNI.swapExactTokensForETH(bal, 1, path, address(this), block.timestamp);
}

Step 4 and 5: sell ETH on Uniswap and repay borrowed stable coin

function repay() external {
    // sell ETH on Uniswap
    address[] memory path = new address[](2);
    path[0] = address(WETH);
    path[1] = address(tokenBorrow);
    UNI.swapExactETHForTokens{value: address(this).balance}(
        1,
        path,
        address(this),
        block.timestamp
    );
    
    // repay borrowed tokens
    uint borrowed = cTokenBorrow.borrowBalanceCurrent(address(this));
    tokenBorrow.approve(address(cTokenBorrow), borrowed);
    require(cTokenBorrow.repayBorrow(borrowed) == 0, "repay failed");

    // Withdraw collateral ETH
    uint supplied = cEth.balanceOfUnderlying(address(this));
    require(cEth.redeemUnderlying(supplied) == 0, "redeem failed");

    // supplied ETH + supplied interest + profit (in token borrow)
}

Test

Test case:

it("should long", async () => {
    // used for debugging
    let tx
    let snap
    // supply
    tx = await testCompound.supply({
        from: ETH_WHALE,
        value: ETH_AMOUNT,
    })

    // long
    snap = await snapshot(testCompound, tokenBorrow)
    console.log(`--- supplied ---`)
    console.log(`liquidity: ${snap.liquidity.div(pow(10, 18))}`)
    console.log(`max borrow: ${snap.maxBorrow.div(pow(10, BORROW_DECIMALS))}`)

    const maxBorrow = await testCompound.getMaxBorrow()
    const borrowAmount = frac(maxBorrow, 50, 100)
    console.log(`borrow amount: ${borrowAmount.div(pow(10, BORROW_DECIMALS))}`)
    tx = await testCompound.long(borrowAmount, {
        from: ETH_WHALE
    })

    // update borrowed balance
    // await testCompound.getBorrowBalance()

    snap = await snapshot(testCompound, tokenBorrow)
    console.log(`--- long ---`)
    console.log(`liquidity: ${snap.liquidity.div(pow(10, 18))}`)
    console.log(`borrowed: ${snap.borrowed.div(pow(10, BORROW_DECIMALS))}`)
    console.log(`eth: ${snap.eth.div(pow(10, 18))}`)

    // accrue interest on borrow
    const block = await web3.eth.getBlockNumber()
    await time.advanceBlockTo(block + 100)

    // repay
    await tokenBorrow.transfer(testCompound.address, BORROW_INTEREST, {
        from: REPAY_WHALE
    })
    tx = await testCompound.repay({
        from: ETH_WHALE,
    })

    snap = await snapshot(testCompound, tokenBorrow)
    console.log(`--- repay ---`)
    console.log(`liquidity: ${snap.liquidity.div(pow(10, 18))}`)
    console.log(`borrowed: ${snap.borrowed.div(pow(10, BORROW_DECIMALS))}`)
    console.log(`eth: ${snap.eth.div(pow(10, 18))}`)
    console.log(`token borrow: ${snap.tokenBorrow.div(pow(10, BORROW_DECIMALS))}`)
})

Last updated