Building QuickBorrow

The Compound Money Market has four primary functions: supply , withdraw , borrow , and repay . QuickBorrow uses all four functions; supply and borrow when sending Ether, and repay and withdraw when closing the position. In both cases, QuickBorrow is combing two money market transactions into one call. In fact, to accurately perform these operations, it is necessary to perform a few other operations.

Let’s explore the Compound interface, using QuickBorrow as our guide.

#Supply

The Compound Protocol (hereinafter “the protocol”) operates solely on ERC-20 tokens. Compound supports Wrapped Ether (WETH). We need to use the Wrapped Ether deposit function in order to get an ERC-20 representation of our Ether. Then we’ll supply the resulting tokenized Ether to Compound!

import "./WrappedEtherInterface.sol";

weth = WrappedEtherInterface("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); weth.deposit.value(msg.value)();

uint supplyStatus = compoundMoneyMarket.supply(weth, msg.value);

require(supplyStatus == 0, "keep reading to find out why supply status has to be 0!");

We wrapped our Ether, and then supplied all of it to Compound. Then we checked that the supply action completed successfully with require(supplyStatus == 0). A full list of Compound status codes can be found here, but suffice it to say that if we don’t get back 0 ( Success ), we want the transaction to revert and the user to keep their Ether!

#Borrow

Having successfully deposited Ether into the protocol, it is time to borrow some tokens! But how much can we borrow? The protocol requires overcollateralization to let users borrow without credit, so we must know the collateral requirement. Also, we can read the current price of the asset ( token / eth ) from the protocol. Therefore, we can calculate the number of tokens we can borrow.

// calculating how much basic attention token to borrow

address BATAddress = "0x0d8775f648430679a709e98d2b0cb6250d2887ef";

uint collateralRatio = compoundMoneyMarket.collateralRatio();

uint assetPrice = compoundMoneyMarket.assetPrices(BATAddress); // it is recommended to use a math library such as SafeMath for this calculation, but for purposes of demonstration...

uint numTokensToBorrow = (msg.value / collateralRatio) * assetPrice;

After calculating the number of tokens we are able to borrow, let’s borrow them!

uint borrowStatus = compoundMoneyMarket.borrow(BATAddress, numTokensToBorrow);

require(borrowStatus == 0, "failed to borrow tokens);

Again, we assert that the operation completed successfully.

Finally, a sample of a single function to allow a contract to supply and borrow from Compound.

import "./WrappedEtherInterface.sol";

import "./MoneyMarketInterface.sol";

import "./EIP20Interface.sol"; contract CDP {

WrappedEtherInterface weth;

MoneyMarketInterface compoundMoneyMarket;

EIP20Interface basicAttentionToken; constructor () {

weth = WrappedEtherInterface("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2");

compoundMoneyMarket = MoneyMarketInterface("0x3fda67f7583380e67ef93072294a7fac882fd7e7");

basicAttentionToken = EIP20Interface(""0x0d8775f648430679a709e98d2b0cb6250d2887ef"); } function fund() payable {

weth.deposit.value(msg.value)();

weth.approve(compoundMoneyMarket, uint(-1)); uint supplyStatus = compoundMoneyMarket.supply(weth, msg.value);

require(supplyStatus == 0, "supply succeeded, keep going!"); uint collateralRatio = compoundMoneyMarket.collateralRatio();

uint assetPrice = compoundMoneyMarket.assetPrices(basicAttentionToken);

uint numTokensToBorrow = (msg.value / collateralRatio) * assetPrice; uint borrowStatus = compoundMoneyMarket.borrow(BATAddress, numTokensToBorrow);

require(borrowStatus == 0, "failed to borrow tokens); // give the borrowed tokens to the user

uint tokenBalance = basicAttentionToken.balanceOf(address(this));

basicAttentionToken.transfer(msg.sender, tokenBalance); }

}

In less than 30 lines of code, we’ve demonstrated how to supply Ether and borrow BAT from the Compound Money Market. There’s more to it than that, though! This contract takes in Ether and borrows tokens, and sends them back to the user, just like QuickBorrow. We also need a way to repay our borrow.

#Repay

To get back our precious Ether posted as collateral, it is necessary to repay the borrowed tokens. The protocol follows the approve-transferFrom pattern to pull tokens into itself, so we’ll follow suit and assume the user of the CDP contract has approved it to transfer the borrowed tokens held by the user on their behalf. After that, it’s a simple matter of calling the protocol’s repayBorrow function. We’ll pass in uint(-1) to signify we want to repay everything we can.

uint borrowBalance = compoundMoneyMarket.getBorrowBalance(address(this), basicAttentionToken); basicAttentionToken.transferFrom(msg.sender, address(this), borrowBalance)

basicAttentionToken.approve(compoundMoneyMarket, uint(-1)); uint repayStatus = compoundMoneyMarket.repayBorrow(borrowedToken, uint(-1));

require(repayStatus == 0, "repay failed");

#Withdraw

Okay! Having made productive use of our borrowed tokens elsewhere, we have repaid our debt to the protocol. Now, time to withdraw our original collateral and be on our way. First, read our balance from the protocol, we’ve probably accrued some interest if we’ve waited a few blocks before withdrawing. Then, we’ll pull it all out, unwrap the weth, and send the Ether back to the user.

uint supplyBalance = compoundMoneyMarket.getSupplyBalance(address(this), weth);

uint withdrawStatus = compoundMoneyMarket.withdraw(weth, supplyBalance);

require(withdrawStatus == 0 , "withdrawal failed"); /* ---------- return ether to user ---------*/

uint wethBalance = weth.balanceOf(address(this)); weth.withdraw(wethBalance); msg.sender.transfer(address(this).balance);

Grouping repaying and withdrawing into one function and adding it to the contract above:

import "./WrappedEtherInterface.sol";

import "./MoneyMarketInterface.sol";

import "./EIP20Interface.sol"; contract CDP {

WrappedEtherInterface weth;

MoneyMarketInterface compoundMoneyMarket;

EIP20Interface basicAttentionToken; constructor () {

weth = WrappedEtherInterface("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2");

compoundMoneyMarket = MoneyMarketInterface("0x3fda67f7583380e67ef93072294a7fac882fd7e7");

basicAttentionToken = EIP20Interface(""0x0d8775f648430679a709e98d2b0cb6250d2887ef"); } function fund() payable {

weth.deposit.value(msg.value)();

weth.approve(compoundMoneyMarket, uint(-1));

uint supplyStatus = compoundMoneyMarket.supply(weth, msg.value);

require(supplyStatus == 0, "supply succeeded, keep going!"); uint collateralRatio = compoundMoneyMarket.collateralRatio();

uint assetPrice = compoundMoneyMarket.assetPrices(basicAttentionToken);

uint numTokensToBorrow = (msg.value / collateralRatio) * assetPrice; uint borrowStatus = compoundMoneyMarket.borrow(basicAttentionToken, numTokensToBorrow);

require(borrowStatus == 0, "failed to borrow tokens); uint tokenBalance = basicAttentionToken.balanceOf(address(this));

basicAttentionToken.transfer(msg.sender, tokenBalance);

} function repay() {

uint borrowBalance = compoundMoneyMarket.getBorrowBalance(address(this), basicAttentionToken);

basicAttentionToken.transferFrom(msg.sender, address(this), borrowBalance)

basicAttentionToken.approve(compoundMoneyMarket, uint(-1)); uint repayStatus = compoundMoneyMarket.repayBorrow(borrowedToken, uint(-1));

require(repayStatus == 0, "repay failed"); uint supplyBalance = compoundMoneyMarket.getSupplyBalance(address(this), weth);

uint withdrawStatus = compoundMoneyMarket.withdraw(weth, supplyBalance);

require(withdrawStatus == 0 , "withdrawal failed"); uint wethBalance = weth.balanceOf(address(this));

weth.withdraw(wethBalance);

msg.sender.transfer(address(this).balance);

}

}

That’s it! In less than 50 lines of Solidity, we are able to deposit Ether in the Compound Money Market, borrow another token, send that one anywhere we like ( back to the user in this case ), then allow the user to repay that borrowed token and receive their Ether back with interest earned.

For a full implementation with more nuanced borrow and withdraw amounts ( maintaining a supply / balance ratio of collateral requirement + 25% ), a newly deployed contract for each new user, and unit tests using mocked interfaces, see the QuickBorrow project on github!