Table of Contents
- About The Project
- Getting Started
- Usage
- Supply and Redeem Contract
- State Variables
- Constructor
- Function supply
- Function getCTokenBalance
- Function getInfo
- Function estimateBalanceOfUnderlying
- Function balanceOfUnderlying
- Function redeem
- Test supply/redeem
- Borrow and Repay Contract
- Collateral
- Account liquidity
- Open price feed
- Enter market and borrow
- Borrowed balance (includes interest)
- Borrow rate
- Repay borrow
- Test borrow/repay
- Liquidate Contract
- Close factor
- Liquidation incentive
- Liquidate
- Test liquidate
- Long and Short
- Long ETH
- Long ETH
- Forking mainnet
- Note
- Roadmap
- Contributing
- License
- Contact
- Acknowledgments
Compound finance allows you to lend and borrow tokens
To get a local copy up and running follow these simple example steps.
-
npm
npm init -y
-
hardhat
npm install --save-dev hardhat
run:
npx hardhat
verify:
npx hardhat verify --network goerli "contract address" "pair address"
-
Clone the repo
git clone https://github.com/Aboudoc/Compound.git
-
Install NPM packages
npm install
-
Dev Dependencies
npm add --save-dev dotenv
If you need testnet funds, use the Alchemy testnet faucet.
This project shows how to interact with Compound protocol
You can find a deep overview of Uniswap v3
in this repo
You can learn more about CPAMM
in this repo
There are 4 operations
related to lending and borrowing on Compound:
- Supply
- Borrow
- Repay
- Redeem
First let's see how to lend
a token to Compound and earn interests
We will start with Supply
and Redeem
Let's see how to supply our token to earn interests. Once we'll want to withdraw our token, then we'll be calling redeem.
Declare the two token interfaces
Set the contract interfaces
When we call this function, it will lend our token to the compound protocol
- Transfer the token from the caller inside this contract
- Approve the
cToken
to spend the token we transfered before frommsg.sender
- Call
mint()
oncToken
contract to lend the token to compound - When
mint()
is called, it returns a number (!0 = there is an error). Check that 0 is returned
The question is: How much cToken
do we have?
Call balanceOf
on cToken
- if you want to know the exchange rate from cToken to the token that we supply, then we can call the function
exchangeRate()
oncToken
- if you want to know the interest rate on supplying the token, then you can get it by calling
supplyRatePerBllock()
oncToken
Note that these two functions are notview
functions, this means we need to send a transaction (paying transaction fee) to get these data However we can get these numbers without snding a transaction, by making astatic call
to these functions
Now we can estimate the balance of the token that we supply
- Return balance of underlying token using balance of
cToken
andexchangeRate
You must take into consideration decimals (wbtc = 8 decimals)
We actually don't have to write this function because compound provides the same function called balanceOfUnderlying
Calculates and returns the amount of token that we supplied into compound.
We'll see that these two function returns numbers really close to each other
To claim the token and the interest
- Check that the function call was successfull by calling
redeem()
oncToken
(!0 = there is an error)
balance of underlying
: We supplied 1 WBTC and the balance is 0.99999 WBTC according to compound
balance of underlying
: The balance icreased, we gain some interest for lending our WBTC
token balance
: We claim the WBTC including the interest rate that we earned
Topics related to this part of the contract:
- collateral
- account liquidity - calculate how much can I borrow?
- open price feed - USD price of token to borrow
- enter market and borrow
- borrowed balance (includes interest)
- borrow rate
- repay borrow
Start by initializing 2 compound contracts: comptroller
and priceFeed
After you supply a token to compound, you are able to borrow a percentage of what you supply: collateral factor
(for exemple 70%)
To get the collateral factor using smart contracts, we need to call markets()
on the comptroller
passing in the address of the cToken that we supply.
It will return 3 outputs: isListed
, colFactor
and isComped
Note that the colFactor
is scaled to 10**18 => divide it by 1e18 to get in %
How much can I borrow?
To get the current liquidity of an account, we need to call getAccountLiquidity()
on the comptroller
passing in the address of this contract
It will return 3 outputs: error
, _liquidity
and _shortfall
Note that _liquidity
: USD amount that we can borrow up to, is scaled up by 10**18
If _shortfall
> 0 => subject to liquidation
In Summary:
- normal circumstance - liquidity > 0 and shortfall == 0
- liquidity > 0 means account can borrow up to
liquidity
- shortfall > 0 is subject to liquidation, you borrowed over the limit
Why might we need the price in terms of USD?
Because liquidity is in terms of USD, by dividing it by the price of token that we want to borrow, we get the amount of tokens that we can borrow.
We can get the price of the token that we borrow in terms of USD by calling getunderlyingPrice
on the contract priceFeed
What to do to borrow the token:
- Enter market
- Check liquidity
- Calculate max borrow
- Borrow 50% of max borrow
- Call
enterMarkets()
oncomptroller
passing in the tokens that we supply. One cToken here, so we initialize an array with 1 element.
- Check
errors[0]
- Call
getAccountLiquidity()
oncomptroller
passing in this address.
- Check
error
,shortfall
andliquidity
- Get the price by calling
getUnderlyingPrice()
onpriceFeed
passing in the tokens that we borrow
- Calculate
maxBorrow
by divinding the liquidity by the price. Scale up liquidity by_decimals
- Check
maxBorrow
-
Define
amount
as 50% ofmaxBorrow
-
Finally call
borrow()
on theCErc20
for the_cTokenBorrow
, and check while calling the function (0 <=> no error)
Once we borrow we can get the balance of the borrowed token including the interest
- Call
borrowBalanceCurrent()
on the cToken that we borrowed, passing in this address
Note that this function is not a view function
Can be interesting to get the borrow rate per block
- Call
borrowRatePerBlock()
on theCErc20
for the cToken that we borrowed
Once we are ready to repay what we've borrowed, we will call the function repay()
- Approve the cToken that we borrowed to be able to spend the token that we borrowed for the
_amount
- Call
repayBorrow
on CErc20 token at the address of_ctTokenBorrowed
passing in the_amount
we wish to repay
col factor
: The collateral factor is 70%supplied
: The amount of amount of WBTC that we supply according to compound is 0.99liquidity
: The amount of token that we can borrow in terms of USD is $0 => because we've not entered a market yetprice
: Price of token we're going to borrow => DAImax borrow
: Amount of token of DAI that we can borrow is 0borrowed balance (compound)
: Amount of DAI that we borrowed is 0
borrowed balalance (erc20)
: After we borrow, the borrowed balance is 10608.03max borrow
: We can further borrow 10618 DAIliquidity
: The liquidity in terms of USD is 10626 at this moment
borrowed balance (compound)
: This shows us that the interest rate on borrow is accruing, it incresed
borrowed balance (compound)
: Is 0liquidity
: The amount of token that we can borrow increased
Liquidate contract
- supply
- borrow max
- wait few blocks and let borrowed balance > supplied balance * col factor
- liquidate
Liquidator contract
- close factor
- liqidation incentive
- liquidate
The maximum pourcentage of the borrow token that can be repaid
For example an account has borrowed 100 DAI and is subject to liquidation
A close factor of 50% means that we can repay up to 50% of the 100 DAI that was borrowed
Call closeFactorMantissa()
on the comptroller
. Divide it buy 10**18 to get it in percentage
When we call liquidate, we pay a portion of the token that was borrowed by another account
In return we are rewarded for portion of the token that was supplied as collateral, and we will receive the collateral at a discount
Call liquidationIncentiveMantissa()
on the comptroller
Call liquidationCalculateSeizeTokens()
on the comptroller
This will return error
and cTokenCollateralAmount
that will be liquidated
Multiply cTokenCollateralAmount
by the exchange rate of the collateral cToken, and you will be able to get the amount of collateral that will be liquidated
seizeAmount
= actualRepayAmount * liquidationIncentive * priceBorrowed / priceCollateral
seizeTokens
= seizeAmount / exchangeRate
= actualRepayAmount * (liquidationIncentive * priceBorrowed) / (priceCollateral * exchangeRate)
- Transfer the
tokenBorrow
frommsg.sender
to this contract for_repayAmount
- Approve the
cTokenBorrow
to be able to spend_repayAmount
from this contract - Finally we cal
liquidateBorrow
oncTokenBorrow
contract passing in the address of the_borrower
(undercollaterize),_cTokeenCollateral
and therepayAmount
. Check that the call is successful (returns 0)
We are going to supply 1 WBTC
then borrow max amount of DAI
We will supply 10 ETH
and borrow 600 DAI
on Compound
(30% of $2000 after shapella, less than the collateral ratio of 70%)
We will sell immediatly our DAI
to ETH
on Uniswap
After ETH price increases, we will sell on Uniswap ETH
to buy back the DAI
borrowed on Compound
Supply 10000 DAI
to Compound and use it as collateral to borrow1.5 ETH
We will immediately sell this ETH
for DAI
on Uniswap
(sell 1.5 ETH => 3000DAI)
After ETH price decreases, we will buy back 1.5 ETH that we borrowed
Here is what we will do:
-
supply ETH
-
borrow stable coin (DAI, USDC)
-
buy ETH on Uniswap
when the price of ETH goes up...
-
sell ETH on Uniswap
-
repay borrowed stable coin
hardhat.config.js
networks: {
hardhat: {
forking: {
url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_API_KEY}`,
},
},
}
Note: Replace the ${}
component of the URL with your personal Alchemy API key.
npx hardhat test test/compoundErc20.test.js
You can find a Quick Start guide below
Supplying Assets to the Compound Protocol
Borrowing Assets from the Compound Protocol
You can find official Compound documentation below:
- [-] Main functionalities of the protocol
- [-] Test on mainnet fork
- [-] Liquidate
- [-] Fix test on liquidate
- [-] Long test
- Short test
- README long short test
- Deploy on mainnet?
- Further reading
See the open issues for a full list of proposed features (and known issues).
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.
If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a star! Thanks again!
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature
) - Commit your Changes (
git commit -m 'Add some AmazingFeature'
) - Push to the Branch (
git push origin feature/AmazingFeature
) - Open a Pull Request
Distributed under the MIT License. See LICENSE.txt
for more information.
Reda Aboutika - @twitter - [email protected]
Project Link: https://github.com/Aboudoc/Compound.git