Skip to content

Commit

Permalink
Merge pull request #18 from clober-dex/feat/clober-v2
Browse files Browse the repository at this point in the history
Feat/clober v2
  • Loading branch information
JhChoy authored Mar 22, 2024
2 parents 529fd4b + a9712eb commit 7178c08
Show file tree
Hide file tree
Showing 29 changed files with 3,344 additions and 44 deletions.
2 changes: 1 addition & 1 deletion contracts/BorrowController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ contract BorrowController is IBorrowController, Controller, IPositionLocker {
);

if (collateralDelta > 0) {
ISubstitute(position.collateralToken).ensureBalance(user, uint256(collateralDelta));
_mintSubstituteAll(position.collateralToken, user, uint256(collateralDelta));
IERC20(position.collateralToken).approve(address(_loanPositionManager), uint256(collateralDelta));
_loanPositionManager.depositToken(position.collateralToken, uint256(collateralDelta));
}
Expand Down
186 changes: 186 additions & 0 deletions contracts/BorrowControllerV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// SPDX-License-Identifier: -
// License: https://license.coupon.finance/LICENSE.pdf

pragma solidity ^0.8.0;

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

import {IBorrowControllerV2} from "./interfaces/IBorrowControllerV2.sol";
import {ILoanPositionManager} from "./interfaces/ILoanPositionManager.sol";
import {ISubstitute} from "./interfaces/ISubstitute.sol";
import {IPositionLocker} from "./interfaces/IPositionLocker.sol";
import {LoanPosition} from "./libraries/LoanPosition.sol";
import {Coupon} from "./libraries/Coupon.sol";
import {Epoch, EpochLibrary} from "./libraries/Epoch.sol";
import {ControllerV2} from "./libraries/ControllerV2.sol";
import {ERC20PermitParams, PermitSignature, PermitParamsLibrary} from "./libraries/PermitParams.sol";

contract BorrowControllerV2 is IBorrowControllerV2, ControllerV2, IPositionLocker {
using PermitParamsLibrary for *;
using EpochLibrary for Epoch;

ILoanPositionManager private immutable _loanPositionManager;
address private immutable _router;

modifier onlyPositionOwner(uint256 positionId) {
if (_loanPositionManager.ownerOf(positionId) != msg.sender) revert InvalidAccess();
_;
}

constructor(
address wrapped1155Factory,
address cloberController,
address bookManager,
address couponManager,
address weth,
address loanPositionManager,
address router
) ControllerV2(wrapped1155Factory, cloberController, bookManager, couponManager, weth) {
_loanPositionManager = ILoanPositionManager(loanPositionManager);
_router = router;
}

function positionLockAcquired(bytes memory data) external returns (bytes memory result) {
if (msg.sender != address(_loanPositionManager)) revert InvalidAccess();

uint256 positionId;
address user;
SwapParams memory swapParams;
(positionId, user, swapParams, data) = abi.decode(data, (uint256, address, SwapParams, bytes));
if (positionId == 0) {
address collateralToken;
address debtToken;
(collateralToken, debtToken, data) = abi.decode(data, (address, address, bytes));
positionId = _loanPositionManager.mint(collateralToken, debtToken);
result = abi.encode(positionId);
}
LoanPosition memory position = _loanPositionManager.getPosition(positionId);

int256 interestThreshold;
(position.collateralAmount, position.debtAmount, position.expiredWith, interestThreshold) =
abi.decode(data, (uint256, uint256, Epoch, int256));

(Coupon[] memory couponsToMint, Coupon[] memory couponsToBurn, int256 collateralDelta, int256 debtDelta) =
_loanPositionManager.adjustPosition(
positionId, position.collateralAmount, position.debtAmount, position.expiredWith
);
if (collateralDelta < 0) {
_loanPositionManager.withdrawToken(position.collateralToken, address(this), uint256(-collateralDelta));
}
if (debtDelta > 0) _loanPositionManager.withdrawToken(position.debtToken, address(this), uint256(debtDelta));
if (couponsToMint.length > 0) {
_loanPositionManager.mintCoupons(couponsToMint, address(this), "");
_wrapCoupons(couponsToMint);
}

if (swapParams.inSubstitute == position.collateralToken) {
_swap(positionId, position.collateralToken, position.debtToken, swapParams.amount, swapParams.data);
} else if (swapParams.inSubstitute == position.debtToken) {
_swap(positionId, position.debtToken, position.collateralToken, swapParams.amount, swapParams.data);
}

_executeCouponTrade(user, positionId, position.debtToken, couponsToMint, couponsToBurn, interestThreshold);

if (collateralDelta > 0) {
_mintSubstituteAll(position.collateralToken, user, uint256(collateralDelta));
IERC20(position.collateralToken).approve(address(_loanPositionManager), uint256(collateralDelta));
_loanPositionManager.depositToken(position.collateralToken, uint256(collateralDelta));
}
if (debtDelta < 0) {
_mintSubstituteAll(position.debtToken, user, uint256(-debtDelta));
IERC20(position.debtToken).approve(address(_loanPositionManager), uint256(-debtDelta));
_loanPositionManager.depositToken(position.debtToken, uint256(-debtDelta));
}
if (couponsToBurn.length > 0) {
_unwrapCoupons(couponsToBurn);
_loanPositionManager.burnCoupons(couponsToBurn);
}

_loanPositionManager.settlePosition(positionId);
}

function borrow(
address collateralToken,
address debtToken,
uint256 collateralAmount,
uint256 debtAmount,
int256 maxPayInterest,
Epoch expiredWith,
SwapParams calldata swapParams,
ERC20PermitParams calldata collateralPermitParams
) external payable nonReentrant wrapAndRefundETH returns (uint256 positionId) {
collateralPermitParams.tryPermit(_getUnderlyingToken(collateralToken), msg.sender, address(this));

bytes memory lockData = abi.encode(collateralAmount, debtAmount, expiredWith, maxPayInterest);
lockData = abi.encode(0, msg.sender, swapParams, abi.encode(collateralToken, debtToken, lockData));
bytes memory result = _loanPositionManager.lock(lockData);
positionId = abi.decode(result, (uint256));

_burnAllSubstitute(collateralToken, msg.sender);
_burnAllSubstitute(debtToken, msg.sender);
_loanPositionManager.transferFrom(address(this), msg.sender, positionId);
}

function adjust(
uint256 positionId,
uint256 collateralAmount,
uint256 debtAmount,
int256 interestThreshold,
Epoch expiredWith,
SwapParams calldata swapParams,
PermitSignature calldata positionPermitParams,
ERC20PermitParams calldata collateralPermitParams,
ERC20PermitParams calldata debtPermitParams
) external payable nonReentrant wrapAndRefundETH onlyPositionOwner(positionId) {
positionPermitParams.tryPermit(_loanPositionManager, positionId, address(this));
LoanPosition memory position = _loanPositionManager.getPosition(positionId);
collateralPermitParams.tryPermit(_getUnderlyingToken(position.collateralToken), msg.sender, address(this));
debtPermitParams.tryPermit(_getUnderlyingToken(position.debtToken), msg.sender, address(this));

position.collateralAmount = collateralAmount;
position.debtAmount = debtAmount;
position.expiredWith = expiredWith;

_loanPositionManager.lock(_encodeAdjustData(positionId, position, interestThreshold, swapParams));

_burnAllSubstitute(position.collateralToken, msg.sender);
_burnAllSubstitute(position.debtToken, msg.sender);
}

function _swap(
uint256 positionId,
address inSubstitute,
address outSubstitute,
uint256 inAmount,
bytes memory swapParams
) internal returns (uint256 outAmount) {
address inToken = ISubstitute(inSubstitute).underlyingToken();
address outToken = ISubstitute(outSubstitute).underlyingToken();
uint256 beforeOutTokenBalance = IERC20(outToken).balanceOf(address(this));

ISubstitute(inSubstitute).burn(inAmount, address(this));
if (inToken == address(_weth)) _weth.deposit{value: inAmount}();
IERC20(inToken).approve(_router, inAmount);
(bool success, bytes memory result) = _router.call(swapParams);
if (!success) revert CollateralSwapFailed(string(result));
IERC20(inToken).approve(_router, 0);

unchecked {
outAmount = IERC20(outToken).balanceOf(address(this)) - beforeOutTokenBalance;
}
emit SwapToken(positionId, inToken, outToken, inAmount, outAmount);

IERC20(outToken).approve(outSubstitute, outAmount);
ISubstitute(outSubstitute).mint(outAmount, address(this));
}

function _encodeAdjustData(
uint256 id,
LoanPosition memory p,
int256 interestThreshold,
SwapParams memory swapParams
) internal view returns (bytes memory) {
bytes memory data = abi.encode(p.collateralAmount, p.debtAmount, p.expiredWith, interestThreshold);
return abi.encode(id, msg.sender, swapParams, data);
}
}
8 changes: 2 additions & 6 deletions contracts/CouponLiquidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -61,19 +61,15 @@ contract CouponLiquidator is ICouponLiquidator, IPositionLocker {
(uint256 liquidationAmount, uint256 repayAmount, uint256 protocolFeeAmount) =
_loanPositionManager.liquidate(positionId, maxRepayAmount);

ISubstitute(position.debtToken).ensureBalance(payer, repayAmount);
ISubstitute(position.debtToken).mintAll(payer, repayAmount);
IERC20(position.debtToken).approve(address(_loanPositionManager), repayAmount);
_loanPositionManager.depositToken(position.debtToken, repayAmount);

uint256 debtAmount = IERC20(outToken).balanceOf(address(this));
if (debtAmount > 0) {
IERC20(outToken).safeTransfer(recipient, debtAmount);
}

uint256 collateralAmount = liquidationAmount - protocolFeeAmount - swapAmount;

_loanPositionManager.withdrawToken(position.collateralToken, address(this), collateralAmount);
_burnAllSubstitute(position.collateralToken, recipient);
_burnAllSubstitute(position.debtToken, recipient);

return "";
}
Expand Down
115 changes: 115 additions & 0 deletions contracts/DepositControllerV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// SPDX-License-Identifier: -
// License: https://license.coupon.finance/LICENSE.pdf

pragma solidity ^0.8.0;

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

import {IDepositControllerV2} from "./interfaces/IDepositControllerV2.sol";
import {IBondPositionManager} from "./interfaces/IBondPositionManager.sol";
import {IPositionLocker} from "./interfaces/IPositionLocker.sol";
import {BondPosition} from "./libraries/BondPosition.sol";
import {Epoch, EpochLibrary} from "./libraries/Epoch.sol";
import {CouponKey} from "./libraries/CouponKey.sol";
import {Coupon} from "./libraries/Coupon.sol";
import {ISubstitute} from "./interfaces/ISubstitute.sol";
import {SubstituteLibrary} from "./libraries/Substitute.sol";
import {ControllerV2} from "./libraries/ControllerV2.sol";
import {ERC20PermitParams, PermitSignature, PermitParamsLibrary} from "./libraries/PermitParams.sol";

contract DepositControllerV2 is IDepositControllerV2, ControllerV2, IPositionLocker {
using PermitParamsLibrary for *;
using EpochLibrary for Epoch;

IBondPositionManager private immutable _bondPositionManager;

modifier onlyPositionOwner(uint256 positionId) {
if (_bondPositionManager.ownerOf(positionId) != msg.sender) revert InvalidAccess();
_;
}

constructor(
address wrapped1155Factory,
address cloberController,
address bookManager,
address couponManager,
address weth,
address bondPositionManager
) ControllerV2(wrapped1155Factory, cloberController, bookManager, couponManager, weth) {
_bondPositionManager = IBondPositionManager(bondPositionManager);
}

function positionLockAcquired(bytes memory data) external returns (bytes memory result) {
if (msg.sender != address(_bondPositionManager)) revert InvalidAccess();

uint256 positionId;
address user;
(positionId, user, data) = abi.decode(data, (uint256, address, bytes));
if (positionId == 0) {
address asset;
(asset, data) = abi.decode(data, (address, bytes));
positionId = _bondPositionManager.mint(asset);
result = abi.encode(positionId);
}
BondPosition memory position = _bondPositionManager.getPosition(positionId);

int256 interestThreshold;
(position.amount, position.expiredWith, interestThreshold) = abi.decode(data, (uint256, Epoch, int256));
(Coupon[] memory couponsToMint, Coupon[] memory couponsToBurn, int256 amountDelta) =
_bondPositionManager.adjustPosition(positionId, position.amount, position.expiredWith);
if (amountDelta < 0) _bondPositionManager.withdrawToken(position.asset, address(this), uint256(-amountDelta));
if (couponsToMint.length > 0) {
_bondPositionManager.mintCoupons(couponsToMint, address(this), "");
_wrapCoupons(couponsToMint);
}

_executeCouponTrade(user, positionId, position.asset, couponsToMint, couponsToBurn, interestThreshold);

if (amountDelta > 0) {
_mintSubstituteAll(position.asset, user, uint256(amountDelta));
IERC20(position.asset).approve(address(_bondPositionManager), uint256(amountDelta));
_bondPositionManager.depositToken(position.asset, uint256(amountDelta));
}
if (couponsToBurn.length > 0) {
_unwrapCoupons(couponsToBurn);
_bondPositionManager.burnCoupons(couponsToBurn);
}

_bondPositionManager.settlePosition(positionId);
}

function deposit(
address asset,
uint256 amount,
Epoch expiredWith,
int256 minEarnInterest,
ERC20PermitParams calldata tokenPermitParams
) external payable nonReentrant wrapAndRefundETH returns (uint256 positionId) {
tokenPermitParams.tryPermit(_getUnderlyingToken(asset), msg.sender, address(this));
bytes memory lockData = abi.encode(amount, expiredWith, -minEarnInterest);
bytes memory result = _bondPositionManager.lock(abi.encode(0, msg.sender, abi.encode(asset, lockData)));
positionId = abi.decode(result, (uint256));

_burnAllSubstitute(asset, msg.sender);

_bondPositionManager.transferFrom(address(this), msg.sender, positionId);
}

function adjust(
uint256 positionId,
uint256 amount,
Epoch expiredWith,
int256 interestThreshold,
ERC20PermitParams calldata tokenPermitParams,
PermitSignature calldata positionPermitParams
) external payable nonReentrant wrapAndRefundETH onlyPositionOwner(positionId) {
positionPermitParams.tryPermit(_bondPositionManager, positionId, address(this));
BondPosition memory position = _bondPositionManager.getPosition(positionId);
tokenPermitParams.tryPermit(position.asset, msg.sender, address(this));

bytes memory lockData = abi.encode(amount, expiredWith, interestThreshold);
_bondPositionManager.lock(abi.encode(positionId, msg.sender, lockData));

_burnAllSubstitute(position.asset, msg.sender);
}
}
2 changes: 1 addition & 1 deletion contracts/SimpleBondController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ contract SimpleBondController is IPositionLocker, ERC1155Holder, ISimpleBondCont
(Coupon[] memory couponsToMint, Coupon[] memory couponsToBurn, int256 amountDelta) =
_bondPositionManager.adjustPosition(tokenId, amount, expiredWith);
if (amountDelta > 0) {
ISubstitute(asset).ensureBalance(user, uint256(amountDelta));
ISubstitute(asset).mintAll(user, uint256(amountDelta));
IERC20(asset).approve(address(_bondPositionManager), uint256(amountDelta));
_bondPositionManager.depositToken(address(asset), uint256(amountDelta));
} else if (amountDelta < 0) {
Expand Down
16 changes: 16 additions & 0 deletions contracts/external/clober-v2/BookId.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity ^0.8.20;

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

type BookId is uint192;

library BookIdLibrary {
function toId(IBookManager.BookKey memory bookKey) internal pure returns (BookId id) {
bytes32 hash = keccak256(abi.encode(bookKey));
assembly {
id := hash
}
}
}
Loading

0 comments on commit 7178c08

Please sign in to comment.