Skip to content

Commit

Permalink
Tokenize Collateral/Borrow/Debt
Browse files Browse the repository at this point in the history
  • Loading branch information
aviggiano committed Dec 11, 2023
1 parent 925c68e commit 6e6979a
Show file tree
Hide file tree
Showing 40 changed files with 641 additions and 583 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ References

- dust amount for loans
- 100% coverage
- replace Vault by ERC20 sToken (this will simplify events) --> beware of address(this)
- test events
- add experiments as tests
- refactor tests following Sablier v2 naming conventions: `test_Foo`, `testFuzz_Foo`, `test_RevertWhen_Foo`, `testFuzz_RevertWhen_Foo`, `testFork_...`
Expand Down
21 changes: 1 addition & 20 deletions src/Size.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,26 +43,7 @@ contract Size is ISize, SizeView, Initializable, Ownable2StepUpgradeable, UUPSUp
_disableInitializers();
}

function initialize(
address _owner,
address _priceFeed,
address _collateralAsset,
address _borrowAsset,
uint256 _crOpening,
uint256 _crLiquidation,
uint256 _collateralPercentagePremiumToLiquidator,
uint256 _collateralPercentagePremiumToBorrower
) public initializer {
InitializeParams memory params = InitializeParams({
owner: _owner,
priceFeed: _priceFeed,
collateralAsset: _collateralAsset,
borrowAsset: _borrowAsset,
crOpening: _crOpening,
crLiquidation: _crLiquidation,
collateralPercentagePremiumToLiquidator: _collateralPercentagePremiumToLiquidator,
collateralPercentagePremiumToBorrower: _collateralPercentagePremiumToBorrower
});
function initialize(InitializeParams calldata params) public initializer {
state.validateInitialize(params);

__Ownable_init(params.owner);
Expand Down
12 changes: 8 additions & 4 deletions src/SizeStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,26 @@ import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IER
import {IPriceFeed} from "@src/oracle/IPriceFeed.sol";
import {Loan} from "@src/libraries/LoanLibrary.sol";
import {User} from "@src/libraries/UserLibrary.sol";
import {Vault} from "@src/libraries/VaultLibrary.sol";
import {CollateralToken} from "@src/token/CollateralToken.sol";
import {BorrowToken} from "@src/token/BorrowToken.sol";
import {DebtToken} from "@src/token/DebtToken.sol";

struct State {
mapping(address => User) users;
Loan[] loans;
IPriceFeed priceFeed;
IERC20Metadata collateralAsset;
IERC20Metadata borrowAsset;
Vault protocolCollateralAsset;
Vault protocolBorrowAsset;
CollateralToken collateralToken;
BorrowToken borrowToken;
DebtToken debtToken;
uint256 maxTime;
uint256 crOpening;
uint256 crLiquidation;
uint256 collateralPercentagePremiumToLiquidator;
uint256 collateralPercentagePremiumToBorrower;
uint256 liquidationProfitCollateralAsset;
address protocolVault;
address feeRecipient;
}

abstract contract SizeStorage {
Expand Down
29 changes: 14 additions & 15 deletions src/SizeView.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,33 @@
pragma solidity 0.8.20;

import {SizeStorage} from "@src/SizeStorage.sol";
import {User, UserLibrary} from "@src/libraries/UserLibrary.sol";
import {User, UserView} from "@src/libraries/UserLibrary.sol";
import {LiquidateLoan} from "@src/libraries/actions/LiquidateLoan.sol";
import {Loan, LoanStatus, LoanLibrary} from "@src/libraries/LoanLibrary.sol";
import {LoanOffer, BorrowOffer, OfferLibrary} from "@src/libraries/OfferLibrary.sol";
import {Vault} from "@src/libraries/VaultLibrary.sol";
import {PERCENT} from "@src/libraries/MathLibrary.sol";

abstract contract SizeView is SizeStorage {
using UserLibrary for User;
using OfferLibrary for LoanOffer;
using OfferLibrary for BorrowOffer;
using LoanLibrary for Loan;

function getCollateralRatio(address user) public view returns (uint256) {
return state.users[user].collateralRatio(state.priceFeed.getPrice());
function collateralRatio(address user) public view returns (uint256) {
return LiquidateLoan.collateralRatio(state, user);
}

function isLiquidatable(address user) public view returns (bool) {
return state.users[user].isLiquidatable(state.priceFeed.getPrice(), state.crLiquidation);
return LiquidateLoan.isLiquidatable(state, user);
}

function isLiquidatable(uint256 loanId) public view returns (bool) {
Loan memory loan = state.loans[loanId];
return state.users[loan.borrower].isLiquidatable(state.priceFeed.getPrice(), state.crLiquidation);
return LiquidateLoan.isLiquidatable(state, loan.borrower);
}

function getAssignedCollateral(uint256 loanId) public view returns (uint256) {
Loan memory loan = state.loans[loanId];
User memory borrower = state.users[loan.borrower];
return borrower.getAssignedCollateral(loan.FV);
return LiquidateLoan.getAssignedCollateral(state, loan);
}

function getDebt(uint256 loanId) public view returns (uint256) {
Expand All @@ -57,12 +55,13 @@ abstract contract SizeView is SizeStorage {
return PERCENT - (state.collateralPercentagePremiumToBorrower + state.collateralPercentagePremiumToLiquidator);
}

function getUser(address user) public view returns (User memory) {
return state.users[user];
}

function getProtocolVault() public view returns (Vault memory, Vault memory) {
return (state.protocolCollateralAsset, state.protocolBorrowAsset);
function getUserView(address user) public view returns (UserView memory) {
return UserView({
user: state.users[user],
collateralAmount: state.collateralToken.balanceOf(user),
borrowAmount: state.borrowToken.balanceOf(user),
debtAmount: state.debtToken.balanceOf(user)
});
}

function activeLoans() public view returns (uint256) {
Expand Down
4 changes: 2 additions & 2 deletions src/interfaces/ISize.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface ISize {
// increases borrower free cash
// if FOL
// increases borrower locked eth
// increases borrower totalDebtCoveredByRealCollateral
// increases borrower debtAmount
// decreases loan offer max amount
// creates new loans
function borrowAsMarketOrder(
Expand Down Expand Up @@ -46,7 +46,7 @@ interface ISize {
// increases protocol free cash
// increases lender claim(???)
// decreases borrower locked eth??
// decreases borrower totalDebtCoveredByRealCollateral
// decreases borrower debtAmount
// sets loan to repaid
function repay(uint256 loanId) external;

Expand Down
1 change: 1 addition & 0 deletions src/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ library Errors {
error PAST_MAX_DUE_DATE(uint256 dueDate);
error DUE_DATE_LOWER_THAN_LOAN_DUE_DATE(uint256 dueDate, uint256 loanDueDate);
error DUE_DATE_GREATER_THAN_MAX_DUE_DATE(uint256 dueDate, uint256 maxDueDate);
error DUE_DATE_OUT_OF_RANTE(uint256 dueDate, uint256 minDueDate, uint256 maxDueDate);
error INVALID_LENDER(address account);
error INVALID_LOAN_OFFER(address lender);
error INVALID_LOAN_STATUS(uint256 loanId, LoanStatus actual, LoanStatus expected);
Expand Down
7 changes: 7 additions & 0 deletions src/libraries/MathLibrary.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,10 @@
pragma solidity 0.8.20;

uint256 constant PERCENT = 1e4;

library MathLibrary {
function valueToWad(uint256 value, uint256 decimals) public pure returns (uint256) {
// @audit protocol does not support tokens with more than 18 decimals
return value * 10 ** (18 - decimals);
}
}
10 changes: 4 additions & 6 deletions src/libraries/OfferLibrary.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {PERCENT} from "@src/libraries/MathLibrary.sol";
import {YieldCurve} from "@src/libraries/YieldCurveLibrary.sol";
import {FixedPointMathLib} from "@solmate/utils/FixedPointMathLib.sol";

import {Errors} from "@src/libraries/Errors.sol";

struct LoanOffer {
uint256 maxAmount;
uint256 maxDueDate;
Expand All @@ -30,10 +32,6 @@ library OfferLibrary {
&& self.curveRelativeTime.rates.length == 0;
}

function getFV(LoanOffer storage self, uint256 amount, uint256 dueDate) public view returns (uint256) {
return FixedPointMathLib.mulDivUp(PERCENT + getRate(self, dueDate), amount, PERCENT);
}

function getRate(LoanOffer memory self, uint256 dueDate) public view returns (uint256) {
return _getRate(self.curveRelativeTime, dueDate);
}
Expand All @@ -43,11 +41,11 @@ library OfferLibrary {
}

function _getRate(YieldCurve memory curveRelativeTime, uint256 dueDate) internal view returns (uint256) {
if (dueDate < block.timestamp) revert OfferLibrary__PastDueDate();
if (dueDate < block.timestamp) revert Errors.PAST_DUE_DATE(dueDate);
uint256 deltaT = dueDate - block.timestamp;
uint256 length = curveRelativeTime.timeBuckets.length;
if (deltaT < curveRelativeTime.timeBuckets[0] || deltaT > curveRelativeTime.timeBuckets[length - 1]) {
revert OfferLibrary__DueDateOutOfRange(
revert Errors.DUE_DATE_OUT_OF_RANTE(
deltaT, curveRelativeTime.timeBuckets[0], curveRelativeTime.timeBuckets[length - 1]
);
} else {
Expand Down
35 changes: 5 additions & 30 deletions src/libraries/UserLibrary.sol
Original file line number Diff line number Diff line change
@@ -1,41 +1,16 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.20;

import {Vault} from "@src/libraries/VaultLibrary.sol";
import {LoanOffer, BorrowOffer} from "@src/libraries/OfferLibrary.sol";
import {PERCENT} from "@src/libraries/MathLibrary.sol";

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

struct User {
Vault collateralAsset;
Vault borrowAsset;
LoanOffer loanOffer;
BorrowOffer borrowOffer;
uint256 totalDebtCoveredByRealCollateral;
}

library UserLibrary {
function collateralRatio(User memory self, uint256 price) public pure returns (uint256) {
return self.totalDebtCoveredByRealCollateral == 0
? type(uint256).max
: FixedPointMathLib.mulDivDown(
FixedPointMathLib.mulDivDown(self.collateralAsset.free, price, self.totalDebtCoveredByRealCollateral),
PERCENT,
1e18 // TODO this is only because the price feed reports prices in 1e18
);
}

function isLiquidatable(User memory self, uint256 price, uint256 crLiquidation) public pure returns (bool) {
return collateralRatio(self, price) < crLiquidation;
}

// solhint-disable-next-line var-name-mixedcase
function getAssignedCollateral(User memory self, uint256 FV) public pure returns (uint256) {
if (self.totalDebtCoveredByRealCollateral == 0) {
return 0;
} else {
return FixedPointMathLib.mulDivDown(self.collateralAsset.free, FV, self.totalDebtCoveredByRealCollateral);
}
}
struct UserView {
User user;
uint256 collateralAmount;
uint256 borrowAmount;
uint256 debtAmount;
}
42 changes: 0 additions & 42 deletions src/libraries/VaultLibrary.sol

This file was deleted.

16 changes: 7 additions & 9 deletions src/libraries/actions/BorrowAsMarketOrder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {User} from "@src/libraries/UserLibrary.sol";
import {Loan} from "@src/libraries/LoanLibrary.sol";
import {OfferLibrary, LoanOffer} from "@src/libraries/OfferLibrary.sol";
import {LoanLibrary, Loan} from "@src/libraries/LoanLibrary.sol";
import {VaultLibrary, Vault} from "@src/libraries/VaultLibrary.sol";
import {PERCENT} from "@src/libraries/MathLibrary.sol";

import {FixedPointMathLib} from "@solmate/utils/FixedPointMathLib.sol";
Expand All @@ -26,7 +25,6 @@ struct BorrowAsMarketOrderParams {

library BorrowAsMarketOrder {
using OfferLibrary for LoanOffer;
using VaultLibrary for Vault;
using LoanLibrary for Loan;
using LoanLibrary for Loan[];

Expand Down Expand Up @@ -100,7 +98,6 @@ library BorrowAsMarketOrder {
// amountIn: Amount of future cashflow to exit
// amountOut: Amount of cash to borrow at present time

User storage borrowerUser = state.users[params.borrower];
User storage lenderUser = state.users[params.lender];

LoanOffer storage loanOffer = lenderUser.loanOffer;
Expand Down Expand Up @@ -130,7 +127,7 @@ library BorrowAsMarketOrder {

state.loans.createSOL(loanId, params.lender, params.borrower, deltaAmountIn);
// NOTE: Transfer deltaAmountOut for each SOL created
lenderUser.borrowAsset.transfer(borrowerUser.borrowAsset, deltaAmountOut);
state.borrowToken.transferFrom(params.lender, params.borrower, deltaAmountOut);
loanOffer.maxAmount -= deltaAmountOut;
amountOutLeft -= deltaAmountOut;
}
Expand All @@ -145,20 +142,21 @@ library BorrowAsMarketOrder {
return;
}

User storage borrowerUser = state.users[params.borrower];
User storage lenderUser = state.users[params.lender];

LoanOffer storage loanOffer = lenderUser.loanOffer;

loanOffer.maxAmount -= params.amount;

uint256 r = PERCENT + loanOffer.getRate(params.dueDate);

// solhint-disable-next-line var-name-mixedcase
uint256 FV = FixedPointMathLib.mulDivUp(r, params.amount, PERCENT);
uint256 maxCollateralToLock = FixedPointMathLib.mulDivUp(FV, state.crOpening, state.priceFeed.getPrice());
borrowerUser.collateralAsset.lock(maxCollateralToLock);
borrowerUser.totalDebtCoveredByRealCollateral += FV;

state.collateralToken.transferFrom(params.borrower, state.protocolVault, maxCollateralToLock); // lock
state.debtToken.mint(params.borrower, FV);
state.loans.createFOL(params.lender, params.borrower, FV, params.dueDate);
lenderUser.borrowAsset.transfer(borrowerUser.borrowAsset, params.amount);
loanOffer.maxAmount -= params.amount;
state.borrowToken.transferFrom(params.lender, params.borrower, params.amount);
}
}
7 changes: 1 addition & 6 deletions src/libraries/actions/Claim.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.20;

import {User} from "@src/libraries/UserLibrary.sol";
import {Loan} from "@src/libraries/LoanLibrary.sol";
import {LoanLibrary, LoanStatus, Loan} from "@src/libraries/LoanLibrary.sol";
import {VaultLibrary, Vault} from "@src/libraries/VaultLibrary.sol";

import {State} from "@src/SizeStorage.sol";

Expand All @@ -18,7 +16,6 @@ struct ClaimParams {

library Claim {
using LoanLibrary for Loan;
using VaultLibrary for Vault;

function validateClaim(State storage state, ClaimParams memory params) external view {
Loan memory loan = state.loans[params.loanId];
Expand All @@ -40,11 +37,9 @@ library Claim {

function executeClaim(State storage state, ClaimParams memory params) external {
Loan storage loan = state.loans[params.loanId];
Vault storage protocolBorrowAsset = state.protocolBorrowAsset;
User storage lenderUser = state.users[loan.lender];

// @audit amountFVExited can increase if SOLs are created, what if claim/exit happen in different times?
protocolBorrowAsset.transfer(lenderUser.borrowAsset, loan.getCredit());
state.borrowToken.transferFrom(state.protocolVault, params.lender, loan.getCredit());
loan.amountFVExited = loan.FV;

emit Events.Claim(params.loanId, params.lender);
Expand Down
Loading

0 comments on commit 6e6979a

Please sign in to comment.