Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cTokens integration #1

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions contracts/adapters/compound/CompoundTokenAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
pragma solidity 0.8.11;

import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import {LibCompound} from "../../libraries/LibCompound.sol";

import {ICERC20} from "../../interfaces/compound/ICERC20.sol";
import {ITokenAdapter} from "../../interfaces/ITokenAdapter.sol";
import {TokenUtils} from "../../libraries/TokenUtils.sol";

contract CompoundTokenAdapter is ITokenAdapter {
/// @dev Compound error code for a noop.
uint256 private constant NO_ERROR = 0;

/// @dev Scalar for all fixed point numbers returned by Compound.
uint256 private constant FIXED_POINT_SCALAR = 1e18;

/// @notice An error used when a call to Compound fails.
///
/// @param code The error code.
error CompoundError(uint256 code);

/// @inheritdoc ITokenAdapter
address public override token;

/// @inheritdoc ITokenAdapter
address public override underlyingToken;

constructor(address _token) {
token = _token;
underlyingToken = ICERC20(token).underlying();
}

/// @inheritdoc ITokenAdapter
function price() external view override returns (uint256) {
return LibCompound.viewExchangeRate(ICERC20(token)) / FIXED_POINT_SCALAR;
}

/// @inheritdoc ITokenAdapter
function wrap(uint256 amount, address recipient, uint256 maximumLoss) external override returns (uint256) {
TokenUtils.safeTransferFrom(underlyingToken, msg.sender, address(this), amount);
TokenUtils.safeApprove(underlyingToken, token, amount);

uint256 startingBalance = TokenUtils.safeBalanceOf(token, address(this));

uint256 error;
if ((error = ICERC20(token).mint(amount)) != NO_ERROR) {
revert CompoundError(error);
}

uint256 endingBalance = TokenUtils.safeBalanceOf(token, address(this));
uint256 mintedAmount = endingBalance - startingBalance;

TokenUtils.safeTransfer(token, recipient, mintedAmount);

return mintedAmount;
}

/// @inheritdoc ITokenAdapter
function unwrap(uint256 amount, address recipient, uint256 maximumLoss) external override returns (uint256) {
TokenUtils.safeTransferFrom(token, msg.sender, address(this), amount);

uint256 startingBalance = TokenUtils.safeBalanceOf(underlyingToken, address(this));

uint256 error;
if ((error = ICERC20(token).redeemUnderlying(amount)) != NO_ERROR) {
revert CompoundError(error);
}

uint256 endingBalance = TokenUtils.safeBalanceOf(underlyingToken, address(this));
uint256 redeemedAmount = endingBalance - startingBalance;

TokenUtils.safeTransfer(underlyingToken, recipient, redeemedAmount);

return redeemedAmount;
}
}
48 changes: 48 additions & 0 deletions contracts/interfaces/compound/ICERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.4;

import {IERC20Minimal} from "../IERC20Minimal.sol";

import {IInterestRateModel} from "./IInterestRateModel.sol";

interface ICERC20 is IERC20Minimal {
function mint(uint256) external returns (uint256);

function borrow(uint256) external returns (uint256);

function underlying() external view returns (address);

function totalBorrows() external view returns (uint256);

function totalFuseFees() external view returns (uint256);

function repayBorrow(uint256) external returns (uint256);

function totalReserves() external view returns (uint256);

function exchangeRateCurrent() external returns (uint256);

function totalAdminFees() external view returns (uint256);

function fuseFeeMantissa() external view returns (uint256);

function adminFeeMantissa() external view returns (uint256);

function exchangeRateStored() external view returns (uint256);

function accrualBlockNumber() external view returns (uint256);

function redeemUnderlying(uint256) external returns (uint256);

function balanceOfUnderlying(address) external returns (uint256);

function reserveFactorMantissa() external view returns (uint256);

function borrowBalanceCurrent(address) external returns (uint256);

function interestRateModel() external view returns (IInterestRateModel);

function initialExchangeRateMantissa() external view returns (uint256);

function repayBorrowBehalf(address, uint256) external returns (uint256);
}
17 changes: 17 additions & 0 deletions contracts/interfaces/compound/IInterestRateModel.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.4;

interface IInterestRateModel {
function getBorrowRate(
uint256,
uint256,
uint256
) external view returns (uint256);

function getSupplyRate(
uint256,
uint256,
uint256,
uint256
) external view returns (uint256);
}
47 changes: 47 additions & 0 deletions contracts/libraries/LibCompound.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.11;

import {IllegalState} from "../base/Errors.sol";

import {FixedPointMathLib} from "./solmate/FixedPointMathLib.sol";

import {IERC20Minimal} from "../interfaces/IERC20Minimal.sol";
import {ICERC20} from "../interfaces/compound/ICERC20.sol";

/// @notice Get up to date cToken data without mutating state.
/// @author Transmissions11 (https://github.com/transmissions11/libcompound)
library LibCompound {
using FixedPointMathLib for uint256;

function viewUnderlyingBalanceOf(ICERC20 cToken, address user) internal view returns (uint256) {
return cToken.balanceOf(user).mulWadDown(viewExchangeRate(cToken));
}

function viewExchangeRate(ICERC20 cToken) internal view returns (uint256) {
uint256 accrualBlockNumberPrior = cToken.accrualBlockNumber();

if (accrualBlockNumberPrior == block.number) return cToken.exchangeRateStored();

uint256 totalCash = IERC20Minimal(cToken.underlying()).balanceOf(address(cToken));
uint256 borrowsPrior = cToken.totalBorrows();
uint256 reservesPrior = cToken.totalReserves();

uint256 borrowRateMantissa = cToken.interestRateModel().getBorrowRate(totalCash, borrowsPrior, reservesPrior);

if (borrowRateMantissa > 0.0005e16) {
revert IllegalState();
}

uint256 interestAccumulated = (borrowRateMantissa * (block.number - accrualBlockNumberPrior)).mulWadDown(
borrowsPrior
);

uint256 totalReserves = cToken.reserveFactorMantissa().mulWadDown(interestAccumulated) + reservesPrior;
uint256 totalBorrows = interestAccumulated + borrowsPrior;
uint256 totalSupply = cToken.totalSupply();

return totalSupply == 0
? cToken.initialExchangeRateMantissa()
: (totalCash + totalBorrows - totalReserves).divWadDown(totalSupply);
}
}
Loading