Skip to content

Commit

Permalink
start compound borrow
Browse files Browse the repository at this point in the history
  • Loading branch information
t4sk committed Jul 10, 2021
1 parent c472907 commit 1afbd42
Show file tree
Hide file tree
Showing 5 changed files with 284 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .env.sample
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
WEB3_INFURA_PROJECT_ID=
ARCHIVE_NODE_API_KEY=
WETH_WHALE=0xee2826453A4Fd5AfeB7ceffeEF3fFA2320081268
DAI_WHALE=0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE
DAI_WHALE=0xF977814e90dA44bFA03b6295A0616a897441aceC
USDC_WHALE=0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE
USDT_WHALE=0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE
WBTC_WHALE=0xF977814e90dA44bFA03b6295A0616a897441aceC
100 changes: 100 additions & 0 deletions contracts/TestCompoundErc20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ pragma solidity ^0.8;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./interfaces/compound.sol";

// supply
// borrow
// repay
// redeem

contract TestCompoundErc20 {
IERC20 public token;
CErc20 public cToken;
Expand Down Expand Up @@ -50,4 +55,99 @@ contract TestCompoundErc20 {
require(cToken.redeem(_cTokenAmount) == 0, "redeem failed");
// cToken.redeemUnderlying(underlying amount);
}

// borrow and repay //
Comptroller public comptroller =
Comptroller(0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B);

PriceFeed public priceFeed = PriceFeed(0x922018674c12a7F0D394ebEEf9B58F186CdE13c1);

// collateral
function getCollateralFactor() external view returns (uint) {
(bool isListed, uint colFactor, bool isComped) = comptroller.markets(
address(cToken)
);
return colFactor; // divide by 1e18 to get in %
}

// account liquidity - calculate how much can I borrow?
// sum of (supplied balance of market entered * col factor) - borrowed
function getAccountLiquidity()
external
view
returns (uint liquidity, uint shortfall)
{
// liquidity and shortfall in USD scaled up by 1e18
(uint error, uint _liquidity, uint _shortfall) = comptroller.getAccountLiquidity(
address(this)
);
require(error == 0, "error");
// shortfall > 0 is subject to liquidation, you borrowed over limit
// liquidity == 0 means account has excess collateral
// normal circumstance - liquidity > 0 and shortfall == 0
return (_liquidity, _shortfall);
}

// open price feed - USD price of token to borrow
function getPriceFeed(address _cToken) external view returns (uint) {
// scaled up by 1e18 + supply token decimals + 2 (USD has 2 decimals)
return priceFeed.getUnderlyingPrice(_cToken);
}

// enter market and borrow
function borrow(address _cTokenToBorrow) external {
// enter market
// enter the supply market so you can borrow another type of asset
address[] memory cTokens = new address[](1);
cTokens[0] = address(cToken);
uint[] memory errors = comptroller.enterMarkets(cTokens);
require(errors[0] == 0, "Comptroller.enterMarkets failed.");

// check liquidity
(uint error, uint liquidity, uint shortfall) = comptroller.getAccountLiquidity(
address(this)
);
require(error == 0, "error");
require(shortfall == 0, "shortfall > 0");
require(liquidity > 0, "liquidity = 0");
// we're going to supply single token so
// liquidity should be close to supplied balance * supplied price * colFactor

// calculate max borrow
uint price = priceFeed.getUnderlyingPrice(_cTokenToBorrow);
// liquidity - scaled up by 1e18
// price - scaled up by 1e18 + token decimals + 2 (USD has 2 decimals)
// uint maxBorrow = ((liquidity * 1e18) / price);
// require(maxBorrow > 0, "max borrow = 0");
uint maxBorrow = 100 * 1e18;

// borrow
uint amount = (maxBorrow * 50) / 100;
require(CErc20(_cTokenToBorrow).borrow(amount) == 0, "borrow failed");
}

// borrowed balance (includes interest)
// not view function
function getBorrowedBalance(address _cTokenBorrowed) external returns (uint) {
return CErc20(_cTokenBorrowed).borrowBalanceCurrent(address(this));
}

// borrow rate
function getBorrowRatePerBlock(address _cTokenBorrowed) external view returns (uint) {
// scaled up by 1e18
return CErc20(_cTokenBorrowed).borrowRatePerBlock();
}

// repay borrow
function repay(
address _tokenBorrowed,
address _cTokenBorrowed,
uint _amount
) external {
IERC20(_tokenBorrowed).approve(_cTokenBorrowed, _amount);
// _amount = 2 **256 - 1 means repay all
require(CErc20(_cTokenBorrowed).repayBorrow(_amount) == 0, "repay failed");
}

// TODO: liquidation ?
}
42 changes: 42 additions & 0 deletions contracts/interfaces/compound.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ interface CErc20 {
function redeem(uint) external returns (uint);

function redeemUnderlying(uint) external returns (uint);

function borrow(uint) external returns (uint);

function borrowBalanceCurrent(address) external returns (uint);

function borrowRatePerBlock() external view returns (uint);

function repayBorrow(uint) external returns (uint);
}

interface CEth {
Expand All @@ -31,4 +39,38 @@ interface CEth {
function redeem(uint) external returns (uint);

function redeemUnderlying(uint) external returns (uint);

function borrow(uint) external returns (uint);

function borrowBalanceCurrent(address) external returns (uint);

function borrowRatePerBlock() external view returns (uint);

function repayBorrow() external payable;
}

interface Comptroller {
function markets(address)
external
view
returns (
bool,
uint,
bool
);

function enterMarkets(address[] calldata) external returns (uint[] memory);

function getAccountLiquidity(address)
external
view
returns (
uint,
uint,
uint
);
}

interface PriceFeed {
function getUnderlyingPrice(address cToken) external view returns (uint);
}
10 changes: 5 additions & 5 deletions test/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ const WBTC = "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"

const WETH_10 = "0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9F"

const DAI_WHALE = "0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE"
const USDC_WHALE = "0xBE0eB53F46cd790Cd13851d5EFf43D12404d33E8"
const USDT_WHALE = "0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE"
const WETH_WHALE = "0xee2826453A4Fd5AfeB7ceffeEF3fFA2320081268"
const WBTC_WHALE = "0xF977814e90dA44bFA03b6295A0616a897441aceC"
const DAI_WHALE = process.env.DAI_WHALE
const USDC_WHALE = process.env.USDC_WHALE
const USDT_WHALE = process.env.USDT_WHALE
const WETH_WHALE = process.env.WETH_WHALE
const WBTC_WHALE = process.env.WBTC_WHALE

// compound
const CDAI = "0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643"
Expand Down
136 changes: 136 additions & 0 deletions test/test-compound-erc20-borrow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
const { time } = require("@openzeppelin/test-helpers")
const assert = require("assert")
const BN = require("bn.js")
const { sendEther, pow } = require("./util")
const { DAI, DAI_WHALE, CDAI, WBTC, WBTC_WHALE, CWBTC } = require("./config")
const { web3 } = require("@openzeppelin/test-helpers/src/setup")

const IERC20 = artifacts.require("IERC20")
const CErc20 = artifacts.require("CErc20")
const TestCompoundErc20 = artifacts.require("TestCompoundErc20")

const SUPPLY_DECIMALS = 8
const SUPPLY_AMOUNT = pow(10, SUPPLY_DECIMALS).mul(new BN(1))
const BORROW_DECIMALS = 18
const BORROW_INTEREST = pow(10, BORROW_DECIMALS).mul(new BN(1000))

contract("TestCompoundErc20", (accounts) => {
const WHALE = WBTC_WHALE
const TOKEN = WBTC
const C_TOKEN = CWBTC
const TOKEN_TO_BORROW = DAI
const C_TOKEN_TO_BORROW = CDAI
const REPAY_WHALE = DAI_WHALE // used to repay interest on borrow

let testCompound
let token
let cToken
let tokenToBorrow
let cTokenToBorrow
beforeEach(async () => {
await sendEther(web3, accounts[0], WHALE, 1)

testCompound = await TestCompoundErc20.new(TOKEN, C_TOKEN)
token = await IERC20.at(TOKEN)
cToken = await CErc20.at(C_TOKEN)
tokenToBorrow = await IERC20.at(TOKEN_TO_BORROW)
cTokenToBorrow = await CErc20.at(C_TOKEN_TO_BORROW)

const supplyBal = await token.balanceOf(WHALE)
console.log(`suuply whale balance: ${supplyBal.div(pow(10, SUPPLY_DECIMALS))}`)
assert(supplyBal.gte(SUPPLY_AMOUNT), "bal < supply")

const borrowBal = await tokenToBorrow.balanceOf(REPAY_WHALE)
console.log(`repay whale balance: ${borrowBal.div(pow(10, BORROW_DECIMALS))}`)
assert(borrowBal.gte(BORROW_INTEREST), "bal < borrow interest")
})

const snapshot = async (testCompound, tokenToBorrow) => {
const { liquidity } = await testCompound.getAccountLiquidity()
const colFactor = await testCompound.getCollateralFactor()
const supplied = await testCompound.balanceOfUnderlying.call()
const price = await testCompound.getPriceFeed(C_TOKEN_TO_BORROW)
const estimateLiquidity = supplied
.mul(price)
.mul(colFactor)
.div(pow(10, SUPPLY_DECIMALS + 18 + SUPPLY_DECIMALS + 2 + 18))
const maxBorrow = liquidity.mul(pow(10, 18 + BORROW_DECIMALS + 2)).div(price)

const borrowedBalance = await testCompound.getBorrowedBalance.call(C_TOKEN_TO_BORROW)
const tokenBal = await tokenToBorrow.balanceOf(testCompound.address)
const borrowRate = await testCompound.getBorrowedBalance.call(C_TOKEN_TO_BORROW)

return {
colFactor: colFactor.div(pow(10, 18 - 2)) / 100,
supplied: supplied.div(pow(10, SUPPLY_DECIMALS - 2)) / 100,
price: price.div(pow(10, 18 + SUPPLY_DECIMALS + 2)),
// TODO: why is this 0?
liquidity: liquidity.div(pow(10, 18)),
estimateLiquidity,
maxBorrow: maxBorrow,
borrowedBalance: borrowedBalance.div(pow(10, BORROW_DECIMALS)),
tokenToBorrow: tokenBal.div(pow(10, BORROW_DECIMALS)),
borrowRate: borrowRate.div(pow(10, 18 - 2)) / 100,
}
}

it("should supply, borrow and repay", async () => {
// supply
await token.approve(testCompound.address, SUPPLY_AMOUNT, { from: WHALE })
await testCompound.supply(SUPPLY_AMOUNT, {
from: WHALE,
})

// borrow

before = await snapshot(testCompound, tokenToBorrow)
console.log(`--- borrow (before) ---`)
console.log(`col factor: ${before.colFactor} %`)
console.log(`supplied: ${before.supplied}`)
console.log(`liquidity: $ ${before.liquidity}`)
console.log(`estimate liquidity: $ ${before.estimateLiquidity}`)
console.log(`price: $ ${before.price}`)
console.log(`max borrow: ${before.maxBorrow}`)
console.log(`borrowed balance (compound): ${before.borrowedBalance}`)
console.log(`borrowed balance (erc20): ${before.tokenToBorrow}`)
console.log(`borrow rate: ${before.borrowRate}`)

testCompound.borrow(C_TOKEN_TO_BORROW, { from: WHALE })

after = await snapshot(testCompound, tokenToBorrow)
console.log(`--- borrow (after) ---`)
console.log(`liquidity: $ ${after.liquidity}`)
console.log(`estimate liquidity: $ ${after.estimateLiquidity}`)
console.log(`max borrow: ${after.maxBorrow}`)
console.log(`borrowed balance (compound): ${before.borrowedBalance}`)
console.log(`borrowed balance (erc20): ${before.tokenToBorrow}`)
console.log(`borrow rate: ${after.borrowRate}`)

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

after = await snapshot(testCompound, cTokenToBorrow)
console.log(`--- after some blocks... ---`)
console.log(`liquidity: $ ${after.liquidity}`)
console.log(`estimate liquidity: $ ${after.estimateLiquidity}`)
console.log(`max borrow: ${after.maxBorrow}`)
console.log(`borrowed balance (compound): ${before.borrowedBalance}`)
console.log(`borrowed balance (erc20): ${before.tokenToBorrow}`)

// repay
tokenToBorrow.transfer(testCompound.address, BORROW_INTEREST, { from: REPAY_WHALE })
const MAX_UINT = pow(2, 256).sub(new BN(1))
tx = await testCompound.repay(TOKEN_TO_BORROW, C_TOKEN_TO_BORROW, MAX_UINT, {
from: REPAY_WHALE,
})

after = await snapshot(testCompound, cTokenToBorrow)

console.log(`--- repay ---`)
console.log(`liquidity: $ ${after.liquidity}`)
console.log(`estimate liquidity: $ ${after.estimateLiquidity}`)
console.log(`max borrow: ${after.maxBorrow}`)
console.log(`borrow balance: ${after.borrowedBalance}`)
})
})

0 comments on commit 1afbd42

Please sign in to comment.