From 6e6979a5f79ac864d80530ecfbfa25f4e11cb3da Mon Sep 17 00:00:00 2001 From: Antonio Guilherme Ferreira Viggiano Date: Tue, 12 Dec 2023 00:07:25 +0100 Subject: [PATCH] Tokenize Collateral/Borrow/Debt --- README.md | 1 - src/Size.sol | 21 +- src/SizeStorage.sol | 12 +- src/SizeView.sol | 29 +-- src/interfaces/ISize.sol | 4 +- src/libraries/Errors.sol | 1 + src/libraries/MathLibrary.sol | 7 + src/libraries/OfferLibrary.sol | 10 +- src/libraries/UserLibrary.sol | 35 +-- src/libraries/VaultLibrary.sol | 42 --- src/libraries/actions/BorrowAsMarketOrder.sol | 16 +- src/libraries/actions/Claim.sol | 7 +- src/libraries/actions/Deposit.sol | 14 +- src/libraries/actions/Exit.sol | 6 +- src/libraries/actions/Initialize.sol | 38 +++ src/libraries/actions/LendAsLimitOrder.sol | 6 +- src/libraries/actions/LendAsMarketOrder.sol | 16 +- src/libraries/actions/LiquidateLoan.sol | 64 +++-- src/libraries/actions/Repay.sol | 13 +- src/libraries/actions/Withdraw.sol | 14 +- src/oracle/IPriceFeed.sol | 1 + src/token/DebtToken.sol | 11 + src/token/NonTransferrableToken.sol | 3 +- test/BaseTest.sol | 84 ++++-- test/BorrowAsLimitOrder.t.sol | 4 +- test/BorrowAsMarketOrder.t.sol | 116 ++++----- test/Claim.t.sol | 4 +- test/Deposit.t.sol | 34 +-- test/Exit.t.sol | 24 +- test/Initialize.t.sol | 63 ++++- test/InitializeValidation.t.sol | 241 ++++++++++-------- test/LendAsLimitOrder.t.sol | 4 +- test/LiquidateLoan.t.sol | 27 +- test/Repay.t.sol | 18 +- test/Upgrade.t.sol | 73 ++++-- test/Withdraw.t.sol | 38 +-- test/helpers/AssertsHelper.sol | 14 +- test/libraries/MathLibrary.sol | 53 ++++ test/libraries/VaultLibrary.t.sol | 53 ---- test/mocks/PriceFeedMock.sol | 3 +- 40 files changed, 641 insertions(+), 583 deletions(-) delete mode 100644 src/libraries/VaultLibrary.sol create mode 100644 src/token/DebtToken.sol create mode 100644 test/libraries/MathLibrary.sol delete mode 100644 test/libraries/VaultLibrary.t.sol diff --git a/README.md b/README.md index 242078cb..64e2302a 100644 --- a/README.md +++ b/README.md @@ -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_...` diff --git a/src/Size.sol b/src/Size.sol index 40020419..c4c42d08 100644 --- a/src/Size.sol +++ b/src/Size.sol @@ -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); diff --git a/src/SizeStorage.sol b/src/SizeStorage.sol index 7687a08e..15a4e966 100644 --- a/src/SizeStorage.sol +++ b/src/SizeStorage.sol @@ -5,7 +5,9 @@ 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; @@ -13,14 +15,16 @@ struct State { 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 { diff --git a/src/SizeView.sol b/src/SizeView.sol index 7bb33f30..1dbfa9ae 100644 --- a/src/SizeView.sol +++ b/src/SizeView.sol @@ -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) { @@ -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) { diff --git a/src/interfaces/ISize.sol b/src/interfaces/ISize.sol index 8e09eee0..056153d1 100644 --- a/src/interfaces/ISize.sol +++ b/src/interfaces/ISize.sol @@ -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( @@ -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; diff --git a/src/libraries/Errors.sol b/src/libraries/Errors.sol index 3417d758..517c1c9f 100644 --- a/src/libraries/Errors.sol +++ b/src/libraries/Errors.sol @@ -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); diff --git a/src/libraries/MathLibrary.sol b/src/libraries/MathLibrary.sol index 6d2a22e8..10283359 100644 --- a/src/libraries/MathLibrary.sol +++ b/src/libraries/MathLibrary.sol @@ -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); + } +} diff --git a/src/libraries/OfferLibrary.sol b/src/libraries/OfferLibrary.sol index ac194b31..cbee0ad7 100644 --- a/src/libraries/OfferLibrary.sol +++ b/src/libraries/OfferLibrary.sol @@ -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; @@ -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); } @@ -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 { diff --git a/src/libraries/UserLibrary.sol b/src/libraries/UserLibrary.sol index 35e52ee1..9f777cdf 100644 --- a/src/libraries/UserLibrary.sol +++ b/src/libraries/UserLibrary.sol @@ -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; } diff --git a/src/libraries/VaultLibrary.sol b/src/libraries/VaultLibrary.sol deleted file mode 100644 index 5bf6af93..00000000 --- a/src/libraries/VaultLibrary.sol +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.20; - -import {Errors} from "@src/libraries/Errors.sol"; - -struct Vault { - uint256 free; - uint256 locked; -} - -library VaultLibrary { - 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); - } - - function lock(Vault storage self, uint256 amount) public { - if (amount > self.free) { - revert Errors.NOT_ENOUGH_FREE_CASH(self.free, amount); - } - - self.free -= amount; - self.locked += amount; - } - - function unlock(Vault storage self, uint256 amount) public { - if (amount > self.locked) { - revert Errors.NOT_ENOUGH_LOCKED_CASH(self.locked, amount); - } - self.locked -= amount; - self.free += amount; - } - - function transfer(Vault storage self, Vault storage other, uint256 amount) public { - if (amount > self.free) { - revert Errors.NOT_ENOUGH_FREE_CASH(self.free, amount); - } - - self.free -= amount; - other.free += amount; - } -} diff --git a/src/libraries/actions/BorrowAsMarketOrder.sol b/src/libraries/actions/BorrowAsMarketOrder.sol index a4a5f3fa..82f2263c 100644 --- a/src/libraries/actions/BorrowAsMarketOrder.sol +++ b/src/libraries/actions/BorrowAsMarketOrder.sol @@ -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"; @@ -26,7 +25,6 @@ struct BorrowAsMarketOrderParams { library BorrowAsMarketOrder { using OfferLibrary for LoanOffer; - using VaultLibrary for Vault; using LoanLibrary for Loan; using LoanLibrary for Loan[]; @@ -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; @@ -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; } @@ -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); } } diff --git a/src/libraries/actions/Claim.sol b/src/libraries/actions/Claim.sol index 10cca098..93c37d25 100644 --- a/src/libraries/actions/Claim.sol +++ b/src/libraries/actions/Claim.sol @@ -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"; @@ -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]; @@ -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); diff --git a/src/libraries/actions/Deposit.sol b/src/libraries/actions/Deposit.sol index 12761235..a23488dc 100644 --- a/src/libraries/actions/Deposit.sol +++ b/src/libraries/actions/Deposit.sol @@ -4,10 +4,10 @@ pragma solidity 0.8.20; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import {User} from "@src/libraries/UserLibrary.sol"; +import {NonTransferrableToken} from "@src/token/NonTransferrableToken.sol"; import {Loan} from "@src/libraries/LoanLibrary.sol"; +import {MathLibrary} from "@src/libraries/MathLibrary.sol"; import {LoanLibrary, Loan} from "@src/libraries/LoanLibrary.sol"; -import {VaultLibrary, Vault} from "@src/libraries/VaultLibrary.sol"; import {State} from "@src/SizeStorage.sol"; @@ -22,7 +22,6 @@ struct DepositParams { library Deposit { using LoanLibrary for Loan; - using VaultLibrary for Vault; using SafeERC20 for IERC20Metadata; function validateDeposit(State storage state, DepositParams memory params) external view { @@ -40,13 +39,14 @@ library Deposit { } function executeDeposit(State storage state, DepositParams memory params) external { - User storage user = state.users[params.account]; - Vault storage vault = params.token == address(state.collateralAsset) ? user.collateralAsset : user.borrowAsset; + NonTransferrableToken nonTransferrableToken = params.token == address(state.collateralAsset) + ? NonTransferrableToken(state.collateralToken) + : NonTransferrableToken(state.borrowToken); IERC20Metadata token = IERC20Metadata(params.token); - uint256 wad = VaultLibrary.valueToWad(params.value, IERC20Metadata(params.token).decimals()); + uint256 wad = MathLibrary.valueToWad(params.value, IERC20Metadata(params.token).decimals()); - vault.free += wad; token.safeTransferFrom(params.account, address(this), params.value); + nonTransferrableToken.mint(params.account, wad); emit Events.Deposit(params.token, wad); } diff --git a/src/libraries/actions/Exit.sol b/src/libraries/actions/Exit.sol index 1292cf04..2474929a 100644 --- a/src/libraries/actions/Exit.sol +++ b/src/libraries/actions/Exit.sol @@ -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, LoanStatus} 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"; @@ -25,7 +24,6 @@ struct ExitParams { library Exit { using OfferLibrary for LoanOffer; - using VaultLibrary for Vault; using LoanLibrary for Loan; using LoanLibrary for Loan[]; @@ -77,8 +75,6 @@ library Exit { // - the other lenders are the makers // The swap traverses the `loanOfferIds` as they if they were ticks with liquidity in an orderbook function executeExit(State storage state, ExitParams memory params) external returns (uint256 amountInLeft) { - User storage exiterUser = state.users[params.exiter]; - emit Events.Exit(params.exiter, params.loanId, params.amount, params.dueDate, params.lendersToExitTo); amountInLeft = params.amount; @@ -105,7 +101,7 @@ library Exit { } state.loans.createSOL(params.loanId, lender, params.exiter, deltaAmountIn); - lenderUser.borrowAsset.transfer(exiterUser.borrowAsset, deltaAmountOut); + state.borrowToken.transferFrom(lender, params.exiter, deltaAmountOut); loanOffer.maxAmount -= deltaAmountOut; amountInLeft -= deltaAmountIn; } diff --git a/src/libraries/actions/Initialize.sol b/src/libraries/actions/Initialize.sol index 8fe08e93..00f1d41d 100644 --- a/src/libraries/actions/Initialize.sol +++ b/src/libraries/actions/Initialize.sol @@ -5,6 +5,9 @@ import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IER import {PERCENT} from "@src/libraries/MathLibrary.sol"; import {Loan} from "@src/libraries/LoanLibrary.sol"; +import {CollateralToken} from "@src/token/CollateralToken.sol"; +import {BorrowToken} from "@src/token/BorrowToken.sol"; +import {DebtToken} from "@src/token/DebtToken.sol"; import {IPriceFeed} from "@src/oracle/IPriceFeed.sol"; @@ -17,10 +20,15 @@ struct InitializeParams { address priceFeed; address collateralAsset; address borrowAsset; + address collateralToken; + address borrowToken; + address debtToken; uint256 crOpening; uint256 crLiquidation; uint256 collateralPercentagePremiumToLiquidator; uint256 collateralPercentagePremiumToBorrower; + address protocolVault; + address feeRecipient; } library Initialize { @@ -45,6 +53,21 @@ library Initialize { revert Errors.NULL_ADDRESS(); } + // validate collateral token + if (params.collateralToken == address(0)) { + revert Errors.NULL_ADDRESS(); + } + + // validate borrow token + if (params.borrowToken == address(0)) { + revert Errors.NULL_ADDRESS(); + } + + // validate debt token + if (params.debtToken == address(0)) { + revert Errors.NULL_ADDRESS(); + } + // validate crOpening if (params.crOpening < PERCENT) { revert Errors.INVALID_COLLATERAL_RATIO(params.crOpening); @@ -72,16 +95,31 @@ library Initialize { params.collateralPercentagePremiumToLiquidator + params.collateralPercentagePremiumToBorrower ); } + + // validate protocolVault + if (params.protocolVault == address(0)) { + revert Errors.NULL_ADDRESS(); + } + + // validate feeRecipient + if (params.feeRecipient == address(0)) { + revert Errors.NULL_ADDRESS(); + } } function executeInitialize(State storage state, InitializeParams memory params) external { state.priceFeed = IPriceFeed(params.priceFeed); state.collateralAsset = IERC20Metadata(params.collateralAsset); state.borrowAsset = IERC20Metadata(params.borrowAsset); + state.collateralToken = CollateralToken(params.collateralToken); + state.borrowToken = BorrowToken(params.borrowToken); + state.debtToken = DebtToken(params.debtToken); state.crOpening = params.crOpening; state.crLiquidation = params.crLiquidation; state.collateralPercentagePremiumToLiquidator = params.collateralPercentagePremiumToLiquidator; state.collateralPercentagePremiumToBorrower = params.collateralPercentagePremiumToBorrower; + state.protocolVault = params.protocolVault; + state.feeRecipient = params.feeRecipient; // NOTE Necessary so that loanIds start at 1, and 0 is reserved for SOLs Loan memory nullLoan; diff --git a/src/libraries/actions/LendAsLimitOrder.sol b/src/libraries/actions/LendAsLimitOrder.sol index 805c6fd1..155a28f5 100644 --- a/src/libraries/actions/LendAsLimitOrder.sol +++ b/src/libraries/actions/LendAsLimitOrder.sol @@ -19,8 +19,6 @@ struct LendAsLimitOrderParams { library LendAsLimitOrder { function validateLendAsLimitOrder(State storage state, LendAsLimitOrderParams memory params) external view { - User memory lenderUser = state.users[params.lender]; - // validate params.lender // N/A @@ -28,8 +26,8 @@ library LendAsLimitOrder { if (params.maxAmount == 0) { revert Errors.NULL_AMOUNT(); } - if (params.maxAmount > lenderUser.borrowAsset.free) { - revert Errors.NOT_ENOUGH_FREE_CASH(lenderUser.borrowAsset.free, params.maxAmount); + if (params.maxAmount > state.borrowToken.balanceOf(params.lender)) { + revert Errors.NOT_ENOUGH_FREE_CASH(state.borrowToken.balanceOf(params.lender), params.maxAmount); } // validate maxDueDate diff --git a/src/libraries/actions/LendAsMarketOrder.sol b/src/libraries/actions/LendAsMarketOrder.sol index 4a65dcd3..a8bd3b6f 100644 --- a/src/libraries/actions/LendAsMarketOrder.sol +++ b/src/libraries/actions/LendAsMarketOrder.sol @@ -1,11 +1,10 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.20; -import {UserLibrary, User} from "@src/libraries/UserLibrary.sol"; +import {User} from "@src/libraries/UserLibrary.sol"; import {Loan} from "@src/libraries/LoanLibrary.sol"; import {OfferLibrary, BorrowOffer} 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"; @@ -25,13 +24,10 @@ struct LendAsMarketOrderParams { library LendAsMarketOrder { using OfferLibrary for BorrowOffer; - using UserLibrary for User; - using VaultLibrary for Vault; using LoanLibrary for Loan[]; function validateLendAsMarketOrder(State storage state, LendAsMarketOrderParams memory params) external view { BorrowOffer memory borrowOffer = state.users[params.borrower].borrowOffer; - User memory lenderUser = state.users[params.lender]; // validate lender @@ -46,8 +42,8 @@ library LendAsMarketOrder { if (params.amount > borrowOffer.maxAmount) { revert Errors.AMOUNT_GREATER_THAN_MAX_AMOUNT(params.amount, borrowOffer.maxAmount); } - if (lenderUser.borrowAsset.free < params.amount) { - revert Errors.NOT_ENOUGH_FREE_CASH(lenderUser.borrowAsset.free, params.amount); + if (state.borrowToken.balanceOf(params.lender) < params.amount) { + revert Errors.NOT_ENOUGH_FREE_CASH(state.borrowToken.balanceOf(params.lender), params.amount); } // validate exactAmountIn @@ -55,8 +51,6 @@ library LendAsMarketOrder { } function executeLendAsMarketOrder(State storage state, LendAsMarketOrderParams memory params) internal { - User storage borrowerUser = state.users[params.borrower]; - User storage lenderUser = state.users[params.lender]; BorrowOffer storage borrowOffer = state.users[params.borrower].borrowOffer; emit Events.LendAsMarketOrder( @@ -75,8 +69,8 @@ library LendAsMarketOrder { amountIn = FixedPointMathLib.mulDivDown(params.amount, PERCENT, r); } - lenderUser.borrowAsset.transfer(borrowerUser.borrowAsset, amountIn); - borrowerUser.totalDebtCoveredByRealCollateral += FV; + state.borrowToken.transferFrom(params.lender, params.borrower, amountIn); + state.debtToken.mint(params.borrower, FV); state.loans.createFOL(params.lender, params.borrower, FV, params.dueDate); borrowOffer.maxAmount -= params.amount; diff --git a/src/libraries/actions/LiquidateLoan.sol b/src/libraries/actions/LiquidateLoan.sol index 158f4c47..42591470 100644 --- a/src/libraries/actions/LiquidateLoan.sol +++ b/src/libraries/actions/LiquidateLoan.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.20; -import {UserLibrary, User} from "@src/libraries/UserLibrary.sol"; +import {FixedPointMathLib} from "@solmate/utils/FixedPointMathLib.sol"; + +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 {PERCENT} from "@src/libraries/MathLibrary.sol"; import {State} from "@src/SizeStorage.sol"; @@ -18,33 +19,46 @@ struct LiquidateLoanParams { } library LiquidateLoan { - using UserLibrary for User; using LoanLibrary for Loan; - using VaultLibrary for Vault; - function _isLiquidatable(State storage state, address account) internal view returns (bool) { - return state.users[account].isLiquidatable(state.priceFeed.getPrice(), state.crLiquidation); + function getAssignedCollateral(State storage state, Loan memory loan) public view returns (uint256) { + uint256 debt = state.debtToken.balanceOf(loan.borrower); + uint256 collateral = state.collateralToken.balanceOf(loan.borrower); + if (debt == 0) { + return 0; + } else { + return FixedPointMathLib.mulDivDown(collateral, loan.FV, debt); + } + } + + function collateralRatio(State storage state, address account) public view returns (uint256) { + uint256 collateral = state.collateralToken.balanceOf(account); + uint256 debt = state.debtToken.balanceOf(account); + uint256 price = state.priceFeed.getPrice(); + uint8 decimals = state.priceFeed.decimals(); + + return debt == 0 + ? type(uint256).max + : FixedPointMathLib.mulDivDown(FixedPointMathLib.mulDivDown(collateral, price, debt), PERCENT, 10 ** decimals); } - function _getAssignedCollateral(State storage state, Loan memory loan) internal view returns (uint256) { - return state.users[loan.borrower].getAssignedCollateral(loan.FV); + function isLiquidatable(State storage state, address account) public view returns (bool) { + return collateralRatio(state, account) < state.crLiquidation; } function validateUserIsNotLiquidatable(State storage state, address account) external view { - if (_isLiquidatable(state, account)) { - revert Errors.USER_IS_LIQUIDATABLE( - account, state.users[account].collateralRatio(state.priceFeed.getPrice()) - ); + if (isLiquidatable(state, account)) { + revert Errors.USER_IS_LIQUIDATABLE(account, collateralRatio(state, account)); } } function validateLiquidateLoan(State storage state, LiquidateLoanParams memory params) external view { Loan memory loan = state.loans[params.loanId]; - uint256 assignedCollateral = _getAssignedCollateral(state, loan); - uint256 amountCollateralDebtCoverage = loan.getDebt() * 1e18 / state.priceFeed.getPrice(); + uint256 assignedCollateral = getAssignedCollateral(state, loan); + uint256 amountCollateralDebtCoverage = (loan.getDebt() * 1e18) / state.priceFeed.getPrice(); // validate loanId - if (!_isLiquidatable(state, loan.borrower)) { + if (!isLiquidatable(state, loan.borrower)) { revert Errors.LOAN_NOT_LIQUIDATABLE(params.loanId); } if (!loan.isFOL()) { @@ -70,27 +84,23 @@ library LiquidateLoan { uint256 price = state.priceFeed.getPrice(); - uint256 assignedCollateral = _getAssignedCollateral(state, loan); + uint256 assignedCollateral = getAssignedCollateral(state, loan); uint256 debtBorrowAsset = loan.getDebt(); - uint256 debtCollateral = debtBorrowAsset * 1e18 / price; + uint256 debtCollateral = (debtBorrowAsset * 1e18) / price; uint256 collateralRemainder = assignedCollateral - debtCollateral; uint256 collateralRemainderToLiquidator = - collateralRemainder * state.collateralPercentagePremiumToLiquidator / PERCENT; + (collateralRemainder * state.collateralPercentagePremiumToLiquidator) / PERCENT; uint256 collateralRemainderToBorrower = - collateralRemainder * state.collateralPercentagePremiumToBorrower / PERCENT; + (collateralRemainder * state.collateralPercentagePremiumToBorrower) / PERCENT; uint256 collateralRemainderToProtocol = collateralRemainder - collateralRemainderToLiquidator - collateralRemainderToBorrower; - state.users[loan.borrower].collateralAsset.transfer( - state.protocolCollateralAsset, collateralRemainderToProtocol + state.collateralToken.transferFrom(loan.borrower, state.feeRecipient, collateralRemainderToProtocol); + state.collateralToken.transferFrom( + loan.borrower, params.liquidator, collateralRemainderToLiquidator + debtCollateral ); - state.users[loan.borrower].collateralAsset.transfer( - state.users[params.liquidator].collateralAsset, collateralRemainderToLiquidator + debtCollateral - ); - state.users[params.liquidator].borrowAsset.transfer(state.protocolBorrowAsset, debtBorrowAsset); - - state.liquidationProfitCollateralAsset += collateralRemainderToProtocol; + state.borrowToken.transferFrom(params.liquidator, state.protocolVault, debtBorrowAsset); return debtCollateral + collateralRemainderToLiquidator; } diff --git a/src/libraries/actions/Repay.sol b/src/libraries/actions/Repay.sol index 0ca1445e..33e90246 100644 --- a/src/libraries/actions/Repay.sol +++ b/src/libraries/actions/Repay.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.20; import {User} from "@src/libraries/UserLibrary.sol"; import {Loan} from "@src/libraries/LoanLibrary.sol"; import {LoanLibrary, Loan} from "@src/libraries/LoanLibrary.sol"; -import {VaultLibrary, Vault} from "@src/libraries/VaultLibrary.sol"; import {State} from "@src/SizeStorage.sol"; @@ -18,11 +17,9 @@ struct RepayParams { library Repay { using LoanLibrary for Loan; - using VaultLibrary for Vault; function validateRepay(State storage state, RepayParams memory params) external view { Loan memory loan = state.loans[params.loanId]; - User memory borrowerUser = state.users[params.borrower]; // validate loanId if (!loan.isFOL()) { @@ -36,8 +33,8 @@ library Repay { if (params.borrower != loan.borrower) { revert Errors.REPAYER_IS_NOT_BORROWER(params.borrower, loan.borrower); } - if (borrowerUser.borrowAsset.free < loan.FV) { - revert Errors.NOT_ENOUGH_FREE_CASH(borrowerUser.borrowAsset.free, loan.FV); + if (state.borrowToken.balanceOf(params.borrower) < loan.FV) { + revert Errors.NOT_ENOUGH_FREE_CASH(state.borrowToken.balanceOf(params.borrower), loan.FV); } // validate protocol @@ -45,11 +42,9 @@ library Repay { function executeRepay(State storage state, RepayParams memory params) external { Loan storage loan = state.loans[params.loanId]; - Vault storage protocolBorrowAsset = state.protocolBorrowAsset; - User storage borrowerUser = state.users[loan.borrower]; - borrowerUser.borrowAsset.transfer(protocolBorrowAsset, loan.FV); - borrowerUser.totalDebtCoveredByRealCollateral -= loan.FV; + state.borrowToken.transferFrom(params.borrower, state.protocolVault, loan.FV); + state.debtToken.burn(loan.borrower, loan.FV); loan.repaid = true; emit Events.Repay(params.loanId, params.borrower); diff --git a/src/libraries/actions/Withdraw.sol b/src/libraries/actions/Withdraw.sol index 3da6ab85..cf71bcd4 100644 --- a/src/libraries/actions/Withdraw.sol +++ b/src/libraries/actions/Withdraw.sol @@ -4,10 +4,10 @@ pragma solidity 0.8.20; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import {User} from "@src/libraries/UserLibrary.sol"; +import {NonTransferrableToken} from "@src/token/NonTransferrableToken.sol"; import {Loan} from "@src/libraries/LoanLibrary.sol"; +import {MathLibrary} from "@src/libraries/MathLibrary.sol"; import {LoanLibrary, Loan} from "@src/libraries/LoanLibrary.sol"; -import {VaultLibrary, Vault} from "@src/libraries/VaultLibrary.sol"; import {State} from "@src/SizeStorage.sol"; @@ -22,7 +22,6 @@ struct WithdrawParams { library Withdraw { using LoanLibrary for Loan; - using VaultLibrary for Vault; using SafeERC20 for IERC20Metadata; function validateWithdraw(State storage state, WithdrawParams memory params) external view { @@ -40,12 +39,13 @@ library Withdraw { } function executeWithdraw(State storage state, WithdrawParams memory params) external { - User storage user = state.users[params.account]; - Vault storage vault = params.token == address(state.collateralAsset) ? user.collateralAsset : user.borrowAsset; + NonTransferrableToken nonTransferrableToken = params.token == address(state.collateralAsset) + ? NonTransferrableToken(state.collateralToken) + : NonTransferrableToken(state.borrowToken); IERC20Metadata token = IERC20Metadata(params.token); - uint256 wad = VaultLibrary.valueToWad(params.value, IERC20Metadata(params.token).decimals()); + uint256 wad = MathLibrary.valueToWad(params.value, IERC20Metadata(params.token).decimals()); - vault.free -= wad; + nonTransferrableToken.burn(params.account, wad); token.safeTransfer(params.account, params.value); emit Events.Withdraw(params.token, wad); diff --git a/src/oracle/IPriceFeed.sol b/src/oracle/IPriceFeed.sol index 68c0b06f..a80adc78 100644 --- a/src/oracle/IPriceFeed.sol +++ b/src/oracle/IPriceFeed.sol @@ -3,4 +3,5 @@ pragma solidity 0.8.20; interface IPriceFeed { function getPrice() external view returns (uint256); + function decimals() external view returns (uint8); } diff --git a/src/token/DebtToken.sol b/src/token/DebtToken.sol new file mode 100644 index 00000000..8f05cbe0 --- /dev/null +++ b/src/token/DebtToken.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.20; + +import {NonTransferrableToken} from "./NonTransferrableToken.sol"; + +contract DebtToken is NonTransferrableToken { + // solhint-disable-next-line no-empty-blocks + constructor(address owner_, string memory name_, string memory symbol_) + NonTransferrableToken(owner_, name_, symbol_) + {} +} diff --git a/src/token/NonTransferrableToken.sol b/src/token/NonTransferrableToken.sol index eb00e24e..439f8aea 100644 --- a/src/token/NonTransferrableToken.sol +++ b/src/token/NonTransferrableToken.sol @@ -3,11 +3,10 @@ pragma solidity 0.8.20; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; import {Errors} from "@src/libraries/Errors.sol"; -contract NonTransferrableToken is Ownable2Step, ERC20 { +contract NonTransferrableToken is Ownable, ERC20 { // solhint-disable-next-line no-empty-blocks constructor(address owner_, string memory name_, string memory symbol_) Ownable(owner_) ERC20(name_, symbol_) {} diff --git a/test/BaseTest.sol b/test/BaseTest.sol index 7e72783a..c334731b 100644 --- a/test/BaseTest.sol +++ b/test/BaseTest.sol @@ -6,13 +6,19 @@ import {Test, console2 as console} from "forge-std/Test.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import {Size} from "../src/Size.sol"; +import {Size} from "@src/Size.sol"; +import {InitializeParams} from "@src/libraries/actions/Initialize.sol"; +import {CollateralToken} from "@src/token/CollateralToken.sol"; +import {BorrowToken} from "@src/token/BorrowToken.sol"; +import {DebtToken} from "@src/token/DebtToken.sol"; import {SizeMock} from "./mocks/SizeMock.sol"; import {PriceFeedMock} from "./mocks/PriceFeedMock.sol"; import {YieldCurve, YieldCurveLibrary} from "@src/libraries/YieldCurveLibrary.sol"; import {AssertsHelper} from "./helpers/AssertsHelper.sol"; -import {User} from "@src/libraries/UserLibrary.sol"; -import {Vault} from "@src/libraries/VaultLibrary.sol"; +import {User, UserView} from "@src/libraries/UserLibrary.sol"; +import {CollateralToken} from "@src/token/CollateralToken.sol"; +import {BorrowToken} from "@src/token/BorrowToken.sol"; +import {DebtToken} from "@src/token/DebtToken.sol"; import {WETH} from "./mocks/WETH.sol"; import {USDC} from "./mocks/USDC.sol"; @@ -23,43 +29,66 @@ contract BaseTest is Test, AssertsHelper { PriceFeedMock public priceFeed; WETH public weth; USDC public usdc; + CollateralToken public collateralToken; + BorrowToken public borrowToken; + DebtToken public debtToken; address public alice = address(0x10000); address public bob = address(0x20000); address public candy = address(0x30000); address public james = address(0x40000); address public liquidator = address(0x50000); + address public protocolVault = address(0x60000); + address public feeRecipient = address(0x70000); address public protocol; struct Vars { - User alice; - User bob; - User candy; - User james; - User liquidator; - Vault protocolCollateralAsset; - Vault protocolBorrowAsset; + UserView alice; + UserView bob; + UserView candy; + UserView james; + UserView liquidator; + uint256 protocolCollateralAmount; + uint256 protocolBorrowAmount; + uint256 feeRecipientCollateralAmount; + uint256 feeRecipientBorrowAmount; } function setUp() public { priceFeed = new PriceFeedMock(address(this)); weth = new WETH(); usdc = new USDC(); + collateralToken = new CollateralToken(address(this), "Size ETH", "szETH"); + borrowToken = new BorrowToken(address(this), "Size USDC", "szUSDC"); + debtToken = new DebtToken(address(this), "Size Debt Token", "szDebt"); + InitializeParams memory params = InitializeParams({ + owner: address(this), + priceFeed: address(priceFeed), + collateralAsset: address(weth), + borrowAsset: address(usdc), + collateralToken: address(collateralToken), + borrowToken: address(borrowToken), + debtToken: address(debtToken), + crOpening: 1.5e4, + crLiquidation: 1.3e4, + collateralPercentagePremiumToLiquidator: 0.3e4, + collateralPercentagePremiumToBorrower: 0.1e4, + protocolVault: protocolVault, + feeRecipient: feeRecipient + }); ERC1967Proxy proxy = new ERC1967Proxy( address(new SizeMock()), - abi.encodeWithSelector( - Size.initialize.selector, - address(this), - address(priceFeed), - address(weth), - address(usdc), - 1.5e4, - 1.3e4, - 0.3e4, - 0.1e4 + abi.encodeCall( + Size.initialize, + (params) ) ); protocol = address(proxy); + + collateralToken.transferOwnership(protocol); + borrowToken.transferOwnership(protocol); + debtToken.transferOwnership(protocol); + size = SizeMock(address(proxy)); vm.label(alice, "alice"); @@ -183,12 +212,15 @@ contract BaseTest is Test, AssertsHelper { } function _state() internal view returns (Vars memory vars) { - vars.alice = size.getUser(alice); - vars.bob = size.getUser(bob); - vars.candy = size.getUser(candy); - vars.james = size.getUser(james); - vars.liquidator = size.getUser(liquidator); - (vars.protocolCollateralAsset, vars.protocolBorrowAsset) = size.getProtocolVault(); + vars.alice = size.getUserView(alice); + vars.bob = size.getUserView(bob); + vars.candy = size.getUserView(candy); + vars.james = size.getUserView(james); + vars.liquidator = size.getUserView(liquidator); + vars.protocolCollateralAmount = collateralToken.balanceOf(protocolVault); + vars.protocolBorrowAmount = borrowToken.balanceOf(protocolVault); + vars.feeRecipientCollateralAmount = collateralToken.balanceOf(feeRecipient); + vars.feeRecipientBorrowAmount = borrowToken.balanceOf(feeRecipient); } function _setPrice(uint256 price) internal { diff --git a/test/BorrowAsLimitOrder.t.sol b/test/BorrowAsLimitOrder.t.sol index cfe96dc1..dd60bd33 100644 --- a/test/BorrowAsLimitOrder.t.sol +++ b/test/BorrowAsLimitOrder.t.sol @@ -24,8 +24,8 @@ contract BorrowAsLimitOrderTest is BaseTest { uint256[] memory rates = new uint256[](2); rates[0] = 1.01e4; rates[1] = 1.02e4; - assertTrue(_state().alice.borrowOffer.isNull()); + assertTrue(_state().alice.user.borrowOffer.isNull()); _borrowAsLimitOrder(alice, 50e18, timeBuckets, rates); - assertTrue(!_state().alice.borrowOffer.isNull()); + assertTrue(!_state().alice.user.borrowOffer.isNull()); } } diff --git a/test/BorrowAsMarketOrder.t.sol b/test/BorrowAsMarketOrder.t.sol index 147a59c6..9f666baa 100644 --- a/test/BorrowAsMarketOrder.t.sol +++ b/test/BorrowAsMarketOrder.t.sol @@ -3,6 +3,8 @@ pragma solidity 0.8.20; import {console2 as console} from "forge-std/console2.sol"; +import {IERC20Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; + import {BaseTest} from "./BaseTest.sol"; import {YieldCurveLibrary} from "@src/libraries/YieldCurveLibrary.sol"; import {User} from "@src/libraries/UserLibrary.sol"; @@ -28,8 +30,7 @@ contract BorrowAsMarketOrderTest is BaseTest { _lendAsLimitOrder(alice, 100e18, 12, 0.03e4, 12); LoanOffer memory offerBefore = size.getLoanOffer(alice); - User memory aliceBefore = size.getUser(alice); - User memory bobBefore = size.getUser(bob); + Vars memory _before = _state(); uint256 amount = 10e18; uint256 dueDate = 12; @@ -38,14 +39,13 @@ contract BorrowAsMarketOrderTest is BaseTest { uint256 debt = FixedPointMathLib.mulDivUp(amount, (PERCENT + 0.03e4), PERCENT); uint256 ethLocked = FixedPointMathLib.mulDivUp(debt, size.crOpening(), priceFeed.getPrice()); - User memory aliceAfter = size.getUser(alice); - User memory bobAfter = size.getUser(bob); + Vars memory _after = _state(); LoanOffer memory offerAfter = size.getLoanOffer(alice); - assertEq(aliceAfter.borrowAsset.free, aliceBefore.borrowAsset.free - amount); - assertEq(bobAfter.borrowAsset.free, bobBefore.borrowAsset.free + amount); - assertEq(bobAfter.collateralAsset.locked, bobBefore.collateralAsset.locked + ethLocked); - assertEq(bobAfter.totalDebtCoveredByRealCollateral, debt); + assertEq(_after.alice.borrowAmount, _before.alice.borrowAmount - amount); + assertEq(_after.bob.borrowAmount, _before.bob.borrowAmount + amount); + assertEq(_after.protocolCollateralAmount, _before.protocolCollateralAmount + ethLocked); + assertEq(_after.bob.debtAmount, debt); assertEq(offerAfter.maxAmount, offerBefore.maxAmount - amount); } @@ -68,20 +68,18 @@ contract BorrowAsMarketOrderTest is BaseTest { _lendAsLimitOrder(alice, MAX_AMOUNT, block.timestamp + MAX_DUE_DATE, rate, MAX_DUE_DATE); LoanOffer memory offerBefore = size.getLoanOffer(alice); - User memory aliceBefore = size.getUser(alice); - User memory bobBefore = size.getUser(bob); + Vars memory _before = _state(); _borrowAsMarketOrder(bob, alice, amount, dueDate); uint256 debt = FixedPointMathLib.mulDivUp(amount, (PERCENT + rate), PERCENT); uint256 ethLocked = FixedPointMathLib.mulDivUp(debt, size.crOpening(), priceFeed.getPrice()); - User memory aliceAfter = size.getUser(alice); - User memory bobAfter = size.getUser(bob); + Vars memory _after = _state(); LoanOffer memory offerAfter = size.getLoanOffer(alice); - assertEq(aliceAfter.borrowAsset.free, aliceBefore.borrowAsset.free - amount); - assertEq(bobAfter.borrowAsset.free, bobBefore.borrowAsset.free + amount); - assertEq(bobAfter.collateralAsset.locked, bobBefore.collateralAsset.locked + ethLocked); - assertEq(bobAfter.totalDebtCoveredByRealCollateral, debt); + assertEq(_after.alice.borrowAmount, _before.alice.borrowAmount - amount); + assertEq(_after.bob.borrowAmount, _before.bob.borrowAmount + amount); + assertEq(_after.protocolCollateralAmount, _before.protocolCollateralAmount + ethLocked); + assertEq(_after.bob.debtAmount, debt); assertEq(offerAfter.maxAmount, offerBefore.maxAmount - amount); } @@ -96,21 +94,17 @@ contract BorrowAsMarketOrderTest is BaseTest { uint256[] memory virtualCollateralLoanIds = new uint256[](1); virtualCollateralLoanIds[0] = loanId; - User memory aliceBefore = size.getUser(alice); - User memory bobBefore = size.getUser(bob); - User memory candyBefore = size.getUser(candy); + Vars memory _before = _state(); uint256 loanId2 = _borrowAsMarketOrder(alice, candy, amount, 12, virtualCollateralLoanIds); - User memory aliceAfter = size.getUser(alice); - User memory bobAfter = size.getUser(bob); - User memory candyAfter = size.getUser(candy); + Vars memory _after = _state(); - assertEq(candyAfter.borrowAsset.free, candyBefore.borrowAsset.free - amount); - assertEq(aliceAfter.borrowAsset.free, aliceBefore.borrowAsset.free + amount); - assertEq(aliceAfter.collateralAsset.locked, aliceBefore.collateralAsset.locked, 0); - assertEq(aliceAfter.totalDebtCoveredByRealCollateral, aliceBefore.totalDebtCoveredByRealCollateral); - assertEq(bobAfter, bobBefore); + assertEq(_after.candy.borrowAmount, _before.candy.borrowAmount - amount); + assertEq(_after.alice.borrowAmount, _before.alice.borrowAmount + amount); + assertEq(_after.protocolCollateralAmount, _before.protocolCollateralAmount); + assertEq(_after.alice.debtAmount, _before.alice.debtAmount); + assertEq(_after.bob, _before.bob); assertTrue(!size.isFOL(loanId2)); } @@ -133,21 +127,17 @@ contract BorrowAsMarketOrderTest is BaseTest { uint256[] memory virtualCollateralLoanIds = new uint256[](1); virtualCollateralLoanIds[0] = loanId; - User memory aliceBefore = size.getUser(alice); - User memory bobBefore = size.getUser(bob); - User memory candyBefore = size.getUser(candy); + Vars memory _before = _state(); uint256 loanId2 = _borrowAsMarketOrder(alice, candy, amount, dueDate, virtualCollateralLoanIds); - User memory aliceAfter = size.getUser(alice); - User memory bobAfter = size.getUser(bob); - User memory candyAfter = size.getUser(candy); + Vars memory _after = _state(); - assertEq(candyAfter.borrowAsset.free, candyBefore.borrowAsset.free - amount); - assertEq(aliceAfter.borrowAsset.free, aliceBefore.borrowAsset.free + amount); - assertEq(aliceAfter.collateralAsset.locked, aliceBefore.collateralAsset.locked, 0); - assertEq(aliceAfter.totalDebtCoveredByRealCollateral, aliceBefore.totalDebtCoveredByRealCollateral); - assertEq(bobAfter, bobBefore); + assertEq(_after.candy.borrowAmount, _before.candy.borrowAmount - amount); + assertEq(_after.alice.borrowAmount, _before.alice.borrowAmount + amount); + assertEq(_after.protocolCollateralAmount, _before.protocolCollateralAmount); + assertEq(_after.alice.debtAmount, _before.alice.debtAmount); + assertEq(_after.bob, _before.bob); assertTrue(!size.isFOL(loanId2)); } @@ -175,12 +165,12 @@ contract BorrowAsMarketOrderTest is BaseTest { uint256 r = PERCENT + loanOffer.getRate(dueDate); uint256 FV = FixedPointMathLib.mulDivUp(r, (amountLoanId2 - amountLoanId1), PERCENT); - uint256 maxETHToLock = FixedPointMathLib.mulDivUp(FV, size.crOpening(), priceFeed.getPrice()); + uint256 maxCollateralToLock = FixedPointMathLib.mulDivUp(FV, size.crOpening(), priceFeed.getPrice()); - assertLt(_after.candy.borrowAsset.free, _before.candy.borrowAsset.free); - assertGt(_after.alice.borrowAsset.free, _before.alice.borrowAsset.free); - assertEq(_after.alice.collateralAsset.locked, _before.alice.collateralAsset.locked + maxETHToLock); - assertEq(_after.alice.totalDebtCoveredByRealCollateral, _before.alice.totalDebtCoveredByRealCollateral + FV); + assertLt(_after.candy.borrowAmount, _before.candy.borrowAmount); + assertGt(_after.alice.borrowAmount, _before.alice.borrowAmount); + assertEq(_after.protocolCollateralAmount, _before.protocolCollateralAmount + maxCollateralToLock); + assertEq(_after.alice.debtAmount, _before.alice.debtAmount + FV); assertEq(_after.bob, _before.bob); assertTrue(size.isFOL(loanId2)); assertEq(loan2.FV, FV); @@ -215,12 +205,13 @@ contract BorrowAsMarketOrderTest is BaseTest { Vars memory _after = _state(); - uint256 maxETHToLock = FixedPointMathLib.mulDivUp(FV, size.crOpening(), priceFeed.getPrice()); + uint256 maxCollateralToLock = FixedPointMathLib.mulDivUp(FV, size.crOpening(), priceFeed.getPrice()); - assertLt(_after.candy.borrowAsset.free, _before.candy.borrowAsset.free); - assertGt(_after.alice.borrowAsset.free, _before.alice.borrowAsset.free); - assertEq(_after.alice.collateralAsset.locked, _before.alice.collateralAsset.locked + maxETHToLock); - assertEq(_after.alice.totalDebtCoveredByRealCollateral, _before.alice.totalDebtCoveredByRealCollateral + FV); + assertLt(_after.candy.borrowAmount, _before.candy.borrowAmount); + assertGt(_after.alice.borrowAmount, _before.alice.borrowAmount); + assertGt(_after.protocolCollateralAmount, _before.protocolCollateralAmount); + assertEq(_after.protocolCollateralAmount, _before.protocolCollateralAmount + maxCollateralToLock); + assertEq(_after.alice.debtAmount, _before.alice.debtAmount + FV); assertEq(_after.bob, _before.bob); assertTrue(size.isFOL(loanId2)); assertEq(size.getLoan(loanId2).FV, FV); @@ -236,21 +227,20 @@ contract BorrowAsMarketOrderTest is BaseTest { uint256[] memory virtualCollateralLoanIds = new uint256[](1); virtualCollateralLoanIds[0] = loanId; - User memory aliceBefore = size.getUser(alice); - User memory bobBefore = size.getUser(bob); - User memory candyBefore = size.getUser(candy); + Vars memory _before = _state(); uint256 loanId2 = _borrowAsMarketOrder(alice, candy, 30e18, 12, virtualCollateralLoanIds); - User memory aliceAfter = size.getUser(alice); - User memory bobAfter = size.getUser(bob); - User memory candyAfter = size.getUser(candy); + // uint256 FV = FixedPointMathLib.mulDivUp(r, amount, PERCENT); + // uint256 maxCollateralToLock = FixedPointMathLib.mulDivUp(FV, size.crOpening(), priceFeed.getPrice()); - assertLt(candyAfter.borrowAsset.free, candyBefore.borrowAsset.free); - assertGt(aliceAfter.borrowAsset.free, aliceBefore.borrowAsset.free); - assertEq(aliceAfter.collateralAsset.locked, aliceBefore.collateralAsset.locked, 0); - assertEq(aliceAfter.totalDebtCoveredByRealCollateral, aliceBefore.totalDebtCoveredByRealCollateral); - assertEq(bobAfter, bobBefore); + Vars memory _after = _state(); + + assertLt(_after.candy.borrowAmount, _before.candy.borrowAmount); + assertGt(_after.alice.borrowAmount, _before.alice.borrowAmount); + assertEq(_after.protocolCollateralAmount, _before.protocolCollateralAmount); + assertEq(_after.alice.debtAmount, _before.alice.debtAmount); + assertEq(_after.bob, _before.bob); assertTrue(!size.isFOL(loanId2)); } @@ -262,10 +252,12 @@ contract BorrowAsMarketOrderTest is BaseTest { uint256 dueDate = 12; uint256 r = PERCENT + loanOffer.getRate(dueDate); uint256 FV = FixedPointMathLib.mulDivUp(r, amount, PERCENT); - uint256 maxETHToLock = FixedPointMathLib.mulDivUp(FV, size.crOpening(), priceFeed.getPrice()); + uint256 maxCollateralToLock = FixedPointMathLib.mulDivUp(FV, size.crOpening(), priceFeed.getPrice()); vm.startPrank(bob); uint256[] memory virtualCollateralLoansIds; - vm.expectRevert(abi.encodeWithSelector(Errors.NOT_ENOUGH_FREE_CASH.selector, 0, maxETHToLock)); + vm.expectRevert( + abi.encodeWithSelector(IERC20Errors.ERC20InsufficientBalance.selector, bob, 0, maxCollateralToLock) + ); size.borrowAsMarketOrder(alice, 100e18, 12, false, virtualCollateralLoansIds); } @@ -281,7 +273,7 @@ contract BorrowAsMarketOrderTest is BaseTest { vm.startPrank(bob); uint256[] memory virtualCollateralLoansIds; - vm.expectRevert(abi.encodeWithSelector(Errors.NOT_ENOUGH_FREE_CASH.selector, 1e18, 10e18)); + vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InsufficientBalance.selector, alice, 1e18, 10e18)); size.borrowAsMarketOrder(alice, amount, dueDate, false, virtualCollateralLoansIds); } } diff --git a/test/Claim.t.sol b/test/Claim.t.sol index 69808d2a..c29e4627 100644 --- a/test/Claim.t.sol +++ b/test/Claim.t.sol @@ -28,7 +28,7 @@ contract ClaimTest is BaseTest { Vars memory _after = _state(); - assertEq(_after.alice.borrowAsset.free, _before.alice.borrowAsset.free + FV); + assertEq(_after.alice.borrowAmount, _before.alice.borrowAmount + FV); assertEq(size.getLoanStatus(loanId), LoanStatus.CLAIMED); } @@ -56,7 +56,7 @@ contract ClaimTest is BaseTest { uint256 FV = FixedPointMathLib.mulDivUp(PERCENT + 0.03e4, 100e18, PERCENT); uint256 credit = FV - amountFVExited; - assertEq(_after.alice.borrowAsset.free, _before.alice.borrowAsset.free + credit); + assertEq(_after.alice.borrowAmount, _before.alice.borrowAmount + credit); assertEq(size.getLoanStatus(loanId), LoanStatus.CLAIMED); } } diff --git a/test/Deposit.t.sol b/test/Deposit.t.sol index 098575e1..82d83a83 100644 --- a/test/Deposit.t.sol +++ b/test/Deposit.t.sol @@ -2,24 +2,20 @@ pragma solidity 0.8.20; import {BaseTest} from "./BaseTest.sol"; -import {User} from "@src/libraries/UserLibrary.sol"; +import {UserView} from "@src/libraries/UserLibrary.sol"; contract DepositTest is BaseTest { function test_Deposit_deposit_increases_user_balance() public { _deposit(alice, address(usdc), 1e6); - User memory aliceUser = size.getUser(alice); - assertEq(aliceUser.borrowAsset.free, 1e18); - assertEq(aliceUser.borrowAsset.locked, 0); - assertEq(aliceUser.collateralAsset.free, 0); - assertEq(aliceUser.collateralAsset.locked, 0); + UserView memory aliceUser = size.getUserView(alice); + assertEq(aliceUser.borrowAmount, 1e18); + assertEq(aliceUser.collateralAmount, 0); assertEq(usdc.balanceOf(address(size)), 1e6); _deposit(alice, address(weth), 2e18); - aliceUser = size.getUser(alice); - assertEq(aliceUser.borrowAsset.free, 1e18); - assertEq(aliceUser.borrowAsset.locked, 0); - assertEq(aliceUser.collateralAsset.free, 2e18); - assertEq(aliceUser.collateralAsset.locked, 0); + aliceUser = size.getUserView(alice); + assertEq(aliceUser.borrowAmount, 1e18); + assertEq(aliceUser.collateralAmount, 2e18); assertEq(weth.balanceOf(address(size)), 2e18); } @@ -28,19 +24,15 @@ contract DepositTest is BaseTest { y = bound(y, 1, type(uint128).max); _deposit(alice, address(usdc), x); - User memory aliceUser = size.getUser(alice); - assertEq(aliceUser.borrowAsset.free, x * 10 ** (18 - usdc.decimals())); - assertEq(aliceUser.borrowAsset.locked, 0); - assertEq(aliceUser.collateralAsset.free, 0); - assertEq(aliceUser.collateralAsset.locked, 0); + UserView memory aliceUser = size.getUserView(alice); + assertEq(aliceUser.borrowAmount, x * 10 ** (18 - usdc.decimals())); + assertEq(aliceUser.collateralAmount, 0); assertEq(usdc.balanceOf(address(size)), x); _deposit(alice, address(weth), y); - aliceUser = size.getUser(alice); - assertEq(aliceUser.borrowAsset.free, x * 10 ** (18 - usdc.decimals())); - assertEq(aliceUser.borrowAsset.locked, 0); - assertEq(aliceUser.collateralAsset.free, y * 10 ** (18 - weth.decimals())); - assertEq(aliceUser.collateralAsset.locked, 0); + aliceUser = size.getUserView(alice); + assertEq(aliceUser.borrowAmount, x * 10 ** (18 - usdc.decimals())); + assertEq(aliceUser.collateralAmount, y * 10 ** (18 - weth.decimals())); assertEq(weth.balanceOf(address(size)), y); } } diff --git a/test/Exit.t.sol b/test/Exit.t.sol index 433d26d8..b16204a8 100644 --- a/test/Exit.t.sol +++ b/test/Exit.t.sol @@ -17,9 +17,7 @@ contract ExitTest is BaseTest { uint256 loanId = _borrowAsMarketOrder(bob, alice, 100e18, 12); _lendAsLimitOrder(candy, 100e18, 12, 0.03e4, 12); - User memory aliceUserBefore = size.getUser(alice); - User memory bobUserBefore = size.getUser(bob); - User memory candyUserBefore = size.getUser(candy); + Vars memory _before = _state(); address[] memory lendersToExitTo = new address[](1); lendersToExitTo[0] = candy; @@ -34,15 +32,13 @@ contract ExitTest is BaseTest { Loan memory loanAfter = size.getLoan(loanId); uint256 loansAfter = size.activeLoans(); - User memory aliceUserAfter = size.getUser(alice); - User memory bobUserAfter = size.getUser(bob); - User memory candyUserAfter = size.getUser(candy); + Vars memory _after = _state(); - assertLt(candyUserAfter.borrowAsset.free, candyUserBefore.borrowAsset.free); - assertGt(aliceUserAfter.borrowAsset.free, aliceUserBefore.borrowAsset.free); + assertLt(_after.candy.borrowAmount, _before.candy.borrowAmount); + assertGt(_after.alice.borrowAmount, _before.alice.borrowAmount); assertLt(loanOfferAfter.maxAmount, loanOfferBefore.maxAmount); assertGt(loanAfter.amountFVExited, loanBefore.amountFVExited); - assertEq(bobUserBefore, bobUserAfter); + assertEq(_before.bob, _after.bob); assertGt(loansAfter, loansBefore); } @@ -54,8 +50,7 @@ contract ExitTest is BaseTest { _lendAsLimitOrder(alice, 100e18, 12, 0.03e4, 12); uint256 loanId = _borrowAsMarketOrder(bob, alice, 30e18, 12); - User memory aliceUserBefore = size.getUser(alice); - User memory bobUserBefore = size.getUser(bob); + Vars memory _before = _state(); address[] memory lendersToExitTo = new address[](1); lendersToExitTo[0] = alice; @@ -70,13 +65,12 @@ contract ExitTest is BaseTest { Loan memory loanAfter = size.getLoan(loanId); uint256 loansAfter = size.activeLoans(); - User memory aliceUserAfter = size.getUser(alice); - User memory bobUserAfter = size.getUser(bob); + Vars memory _after = _state(); - assertEq(aliceUserAfter, aliceUserBefore); + assertEq(_before.alice, _after.alice); assertLt(loanOfferAfter.maxAmount, loanOfferBefore.maxAmount); assertGt(loanAfter.amountFVExited, loanBefore.amountFVExited); - assertEq(bobUserBefore, bobUserAfter); + assertEq(_after.bob, _before.bob); assertGt(loansAfter, loansBefore); } } diff --git a/test/Initialize.t.sol b/test/Initialize.t.sol index 258a3033..c3c7341f 100644 --- a/test/Initialize.t.sol +++ b/test/Initialize.t.sol @@ -6,6 +6,10 @@ import {Test} from "forge-std/Test.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {Size} from "@src/Size.sol"; +import {InitializeParams} from "@src/libraries/actions/Initialize.sol"; +import {CollateralToken} from "@src/token/CollateralToken.sol"; +import {BorrowToken} from "@src/token/BorrowToken.sol"; +import {DebtToken} from "@src/token/DebtToken.sol"; import {PriceFeedMock} from "./mocks/PriceFeedMock.sol"; import {WETH} from "./mocks/WETH.sol"; import {USDC} from "./mocks/USDC.sol"; @@ -16,37 +20,70 @@ contract InitializeTest is Test { PriceFeedMock public priceFeed; WETH public weth; USDC public usdc; + CollateralToken public collateralToken; + BorrowToken public borrowToken; + DebtToken public debtToken; + address public protocolVault; + address public feeRecipient; function setUp() public { priceFeed = new PriceFeedMock(address(this)); weth = new WETH(); usdc = new USDC(); + collateralToken = new CollateralToken(address(this), "Size ETH", "szETH"); + borrowToken = new BorrowToken( address(this), "Size USDC", "szUSDC"); + debtToken = new DebtToken(address(this), "Size Debt Token", "szDebt"); + protocolVault = makeAddr("protocolVault"); + feeRecipient = makeAddr("feeRecipient"); } function test_SizeInitialize_implementation_cannot_be_initialized() public { implementation = new Size(); vm.expectRevert(); - implementation.initialize( - address(this), address(priceFeed), address(weth), address(usdc), 1.5e4, 1.3e4, 0.3e4, 0.1e4 - ); + InitializeParams memory params = InitializeParams({ + owner: address(this), + priceFeed: address(priceFeed), + collateralAsset: address(weth), + borrowAsset: address(usdc), + collateralToken: address(collateralToken), + borrowToken: address(borrowToken), + debtToken: address(debtToken), + crOpening: 1.5e4, + crLiquidation: 1.3e4, + collateralPercentagePremiumToLiquidator: 0.3e4, + collateralPercentagePremiumToBorrower: 0.1e4, + protocolVault: protocolVault, + feeRecipient: feeRecipient + }); + implementation.initialize(params); assertEq(implementation.crLiquidation(), 0); } function test_SizeInitialize_proxy_can_be_initialized() public { implementation = new Size(); + InitializeParams memory params = InitializeParams( + address(this), + address(priceFeed), + address(weth), + address(usdc), + address(collateralToken), + address(borrowToken), + address(debtToken), + 1.5e4, + 1.3e4, + 0.3e4, + 0.1e4, + protocolVault, + feeRecipient + ); proxy = new ERC1967Proxy( address(implementation), - abi.encodeWithSelector( - Size.initialize.selector, - address(this), - address(priceFeed), - address(weth), - address(usdc), - 1.5e4, - 1.3e4, - 0.3e4, - 0.1e4 + abi.encodeCall( + Size.initialize, + ( + params + ) ) ); diff --git a/test/InitializeValidation.t.sol b/test/InitializeValidation.t.sol index b1d4784d..ea191a39 100644 --- a/test/InitializeValidation.t.sol +++ b/test/InitializeValidation.t.sol @@ -6,6 +6,10 @@ import {Test} from "forge-std/Test.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {Size} from "@src/Size.sol"; +import {InitializeParams} from "@src/libraries/actions/Initialize.sol"; +import {CollateralToken} from "@src/token/CollateralToken.sol"; +import {BorrowToken} from "@src/token/BorrowToken.sol"; +import {DebtToken} from "@src/token/DebtToken.sol"; import {PriceFeedMock} from "./mocks/PriceFeedMock.sol"; import {WETH} from "./mocks/WETH.sol"; import {USDC} from "./mocks/USDC.sol"; @@ -18,174 +22,207 @@ contract InitializeValidationTest is Test { PriceFeedMock public priceFeed; WETH public weth; USDC public usdc; + CollateralToken public collateralToken; + BorrowToken public borrowToken; + DebtToken public debtToken; + address public protocolVault; + address public feeRecipient; function setUp() public { priceFeed = new PriceFeedMock(address(this)); weth = new WETH(); usdc = new USDC(); + collateralToken = new CollateralToken(address(this), "Size ETH", "szETH"); + borrowToken = new BorrowToken( address(this), "Size USDC", "szUSDC"); + debtToken = new DebtToken(address(this), "Size Debt Token", "szDebt"); } function test_SizeInitializeValidation() public { implementation = new Size(); + InitializeParams memory params = InitializeParams({ + owner: address(this), + priceFeed: address(priceFeed), + collateralAsset: address(weth), + borrowAsset: address(usdc), + collateralToken: address(collateralToken), + borrowToken: address(borrowToken), + debtToken: address(debtToken), + crOpening: 1.5e4, + crLiquidation: 1.3e4, + collateralPercentagePremiumToLiquidator: 0.3e4, + collateralPercentagePremiumToBorrower: 0.1e4, + protocolVault: protocolVault, + feeRecipient: feeRecipient + }); + + params.owner = address(0); vm.expectRevert(abi.encodeWithSelector(Errors.NULL_ADDRESS.selector)); proxy = new ERC1967Proxy( address(implementation), - abi.encodeWithSelector( - Size.initialize.selector, - address(0), - address(priceFeed), - address(weth), - address(usdc), - 1.5e4, - 1.3e4, - 0.3e4, - 0.1e4 + abi.encodeCall( + Size.initialize, + (params) ) ); + params.owner = address(this); + params.priceFeed = address(0); vm.expectRevert(abi.encodeWithSelector(Errors.NULL_ADDRESS.selector)); proxy = new ERC1967Proxy( address(implementation), - abi.encodeWithSelector( - Size.initialize.selector, - address(this), - address(0), - address(weth), - address(usdc), - 1.5e4, - 1.3e4, - 0.3e4, - 0.1e4 + abi.encodeCall( + Size.initialize, + (params) ) ); + params.priceFeed = address(priceFeed); + params.collateralAsset = address(0); vm.expectRevert(abi.encodeWithSelector(Errors.NULL_ADDRESS.selector)); proxy = new ERC1967Proxy( address(implementation), - abi.encodeWithSelector( - Size.initialize.selector, - address(this), - address(priceFeed), - address(0), - address(usdc), - 1.5e4, - 1.3e4, - 0.3e4, - 0.1e4 + abi.encodeCall( + Size.initialize, + (params) ) ); + params.collateralAsset = address(weth); + params.borrowAsset = address(0); vm.expectRevert(abi.encodeWithSelector(Errors.NULL_ADDRESS.selector)); proxy = new ERC1967Proxy( address(implementation), - abi.encodeWithSelector( - Size.initialize.selector, - address(this), - address(priceFeed), - address(weth), - address(0), - 1.5e4, - 1.3e4, - 0.3e4, - 0.1e4 + abi.encodeCall( + Size.initialize, + (params) ) ); + params.borrowAsset = address(usdc); - vm.expectRevert(abi.encodeWithSelector(Errors.INVALID_COLLATERAL_RATIO.selector, 0.5e4)); + params.collateralToken = address(0); + vm.expectRevert(abi.encodeWithSelector(Errors.NULL_ADDRESS.selector)); proxy = new ERC1967Proxy( address(implementation), - abi.encodeWithSelector( - Size.initialize.selector, - address(this), - address(priceFeed), - address(weth), - address(usdc), - 0.5e4, - 1.3e4, - 0.3e4, - 0.1e4 + abi.encodeCall( + Size.initialize, + (params) ) ); + params.collateralToken = address(collateralToken); - vm.expectRevert(abi.encodeWithSelector(Errors.INVALID_COLLATERAL_RATIO.selector, 0.3e4)); + params.borrowToken = address(0); + vm.expectRevert(abi.encodeWithSelector(Errors.NULL_ADDRESS.selector)); proxy = new ERC1967Proxy( address(implementation), - abi.encodeWithSelector( - Size.initialize.selector, - address(this), - address(priceFeed), - address(weth), - address(usdc), - 1.5e4, - 0.3e4, - 0.3e4, - 0.1e4 + abi.encodeCall( + Size.initialize, + (params) ) ); + params.borrowToken = address(borrowToken); + + params.debtToken = address(0); + vm.expectRevert(abi.encodeWithSelector(Errors.NULL_ADDRESS.selector)); + proxy = new ERC1967Proxy( + address(implementation), + abi.encodeCall( + Size.initialize, + (params) + ) + ); + params.debtToken = address(debtToken); + + params.crOpening = 0.5e4; + vm.expectRevert(abi.encodeWithSelector(Errors.INVALID_COLLATERAL_RATIO.selector, 0.5e4)); + proxy = new ERC1967Proxy( + address(implementation), + abi.encodeCall( + Size.initialize, + (params) + ) + ); + params.crOpening = 1.5e4; + + params.crLiquidation = 0.3e4; + vm.expectRevert(abi.encodeWithSelector(Errors.INVALID_COLLATERAL_RATIO.selector, 0.3e4)); + proxy = new ERC1967Proxy( + address(implementation), + abi.encodeCall( + Size.initialize, + (params) + ) + ); + params.crLiquidation = 1.3e4; + params.crLiquidation = 1.5e4; + params.crOpening = 1.3e4; vm.expectRevert(abi.encodeWithSelector(Errors.INVALID_LIQUIDATION_COLLATERAL_RATIO.selector, 1.3e4, 1.5e4)); proxy = new ERC1967Proxy( address(implementation), - abi.encodeWithSelector( - Size.initialize.selector, - address(this), - address(priceFeed), - address(weth), - address(usdc), - 1.3e4, - 1.5e4, - 0.3e4, - 0.1e4 + abi.encodeCall( + Size.initialize, + (params) ) ); + params.crLiquidation = 1.3e4; + params.crOpening = 1.5e4; + params.collateralPercentagePremiumToLiquidator = 1.1e4; vm.expectRevert(abi.encodeWithSelector(Errors.INVALID_COLLATERAL_PERCENTAGE_PREMIUM.selector, 1.1e4)); proxy = new ERC1967Proxy( address(implementation), - abi.encodeWithSelector( - Size.initialize.selector, - address(this), - address(priceFeed), - address(weth), - address(usdc), - 1.5e4, - 1.3e4, - 1.1e4, - 0.1e4 + abi.encodeCall( + Size.initialize, + (params) ) ); + params.collateralPercentagePremiumToLiquidator = 0.3e4; + params.collateralPercentagePremiumToBorrower = 1.2e4; vm.expectRevert(abi.encodeWithSelector(Errors.INVALID_COLLATERAL_PERCENTAGE_PREMIUM.selector, 1.2e4)); proxy = new ERC1967Proxy( address(implementation), - abi.encodeWithSelector( - Size.initialize.selector, - address(this), - address(priceFeed), - address(weth), - address(usdc), - 1.5e4, - 1.3e4, - 0.3e4, - 1.2e4 + abi.encodeCall( + Size.initialize, + (params) ) ); + params.collateralPercentagePremiumToBorrower = 0.1e4; + params.collateralPercentagePremiumToLiquidator = 0.6e4; + params.collateralPercentagePremiumToBorrower = 0.6e4; vm.expectRevert(abi.encodeWithSelector(Errors.INVALID_COLLATERAL_PERCENTAGE_PREMIUM_SUM.selector, 1.2e4)); proxy = new ERC1967Proxy( address(implementation), - abi.encodeWithSelector( - Size.initialize.selector, - address(this), - address(priceFeed), - address(weth), - address(usdc), - 1.5e4, - 1.3e4, - 0.6e4, - 0.6e4 + abi.encodeCall( + Size.initialize, + (params) + ) + ); + params.collateralPercentagePremiumToLiquidator = 0.3e4; + params.collateralPercentagePremiumToBorrower = 0.1e4; + + params.protocolVault = address(0); + vm.expectRevert(abi.encodeWithSelector(Errors.NULL_ADDRESS.selector)); + proxy = new ERC1967Proxy( + address(implementation), + abi.encodeCall( + Size.initialize, + (params) + ) + ); + params.protocolVault = protocolVault; + + params.feeRecipient = address(0); + vm.expectRevert(abi.encodeWithSelector(Errors.NULL_ADDRESS.selector)); + proxy = new ERC1967Proxy( + address(implementation), + abi.encodeCall( + Size.initialize, + (params) ) ); + params.feeRecipient = feeRecipient; } } diff --git a/test/LendAsLimitOrder.t.sol b/test/LendAsLimitOrder.t.sol index fcec1dec..b184e4ed 100644 --- a/test/LendAsLimitOrder.t.sol +++ b/test/LendAsLimitOrder.t.sol @@ -18,8 +18,8 @@ contract LendAsLimitOrderTest is BaseTest { function test_LendAsLimitOrder_lendAsLimitOrder_adds_loanOffer_to_orderbook() public { _deposit(alice, 100e18, 100e18); - assertTrue(_state().alice.loanOffer.isNull()); + assertTrue(_state().alice.user.loanOffer.isNull()); _lendAsLimitOrder(alice, 50e18, 12, 1.01e4, 12); - assertTrue(!_state().alice.loanOffer.isNull()); + assertTrue(!_state().alice.user.loanOffer.isNull()); } } diff --git a/test/LiquidateLoan.t.sol b/test/LiquidateLoan.t.sol index 409f9363..07d552fa 100644 --- a/test/LiquidateLoan.t.sol +++ b/test/LiquidateLoan.t.sol @@ -16,7 +16,7 @@ contract LiquidateLoanTest is BaseTest { _deposit(bob, 100e18, 100e18); _deposit(liquidator, 100e18, 100e18); - assertEq(size.getCollateralRatio(bob), type(uint256).max); + assertEq(size.collateralRatio(bob), type(uint256).max); _lendAsLimitOrder(alice, 100e18, 12, 0.03e4, 12); uint256 amount = 15e18; @@ -27,7 +27,7 @@ contract LiquidateLoanTest is BaseTest { assertEq(size.getAssignedCollateral(loanId), assigned); assertEq(size.getDebt(loanId), debt); - assertEq(size.getCollateralRatio(bob), assigned * 1e4 / (debt * 1)); + assertEq(size.collateralRatio(bob), assigned * 1e4 / (debt * 1)); assertTrue(!size.isLiquidatable(bob)); assertTrue(!size.isLiquidatable(loanId)); @@ -35,7 +35,7 @@ contract LiquidateLoanTest is BaseTest { assertEq(size.getAssignedCollateral(loanId), assigned); assertEq(size.getDebt(loanId), debt); - assertEq(size.getCollateralRatio(bob), assigned * 1e4 / (debt * 5)); + assertEq(size.collateralRatio(bob), assigned * 1e4 / (debt * 5)); assertTrue(size.isLiquidatable(bob)); assertTrue(size.isLiquidatable(loanId)); @@ -47,24 +47,25 @@ contract LiquidateLoanTest is BaseTest { Vars memory _after = _state(); - assertEq(_after.liquidator.borrowAsset.free, _before.liquidator.borrowAsset.free - debt); - assertEq(_after.protocolBorrowAsset.free, _before.protocolBorrowAsset.free + debt); + assertEq(_after.liquidator.borrowAmount, _before.liquidator.borrowAmount - debt); + assertEq(_after.protocolBorrowAmount, _before.protocolBorrowAmount + debt); assertEq( - _after.protocolCollateralAsset.free, - _before.protocolCollateralAsset.free - + collateralRemainder * size.collateralPercentagePremiumToProtocol() / PERCENT + _after.feeRecipientCollateralAmount, + _before.feeRecipientCollateralAmount + + collateralRemainder * size.collateralPercentagePremiumToProtocol() / PERCENT, + "here" ); assertEq( - _after.bob.collateralAsset.free, - _before.bob.collateralAsset.free - (debt * 5) + _after.bob.collateralAmount, + _before.bob.collateralAmount - (debt * 5) - collateralRemainder * (size.collateralPercentagePremiumToProtocol() + size.collateralPercentagePremiumToLiquidator()) / PERCENT, - _before.bob.collateralAsset.free - (debt * 5) - collateralRemainder + _before.bob.collateralAmount - (debt * 5) - collateralRemainder + collateralRemainder * size.collateralPercentagePremiumToBorrower() / PERCENT ); assertEq( - _after.liquidator.collateralAsset.free, - _before.liquidator.collateralAsset.free + (debt * 5) + _after.liquidator.collateralAmount, + _before.liquidator.collateralAmount + (debt * 5) + collateralRemainder * size.collateralPercentagePremiumToLiquidator() / PERCENT ); } diff --git a/test/Repay.t.sol b/test/Repay.t.sol index 8323fb60..2e932ab3 100644 --- a/test/Repay.t.sol +++ b/test/Repay.t.sol @@ -28,9 +28,9 @@ contract RepayTest is BaseTest { Vars memory _after = _state(); - assertEq(_after.bob.totalDebtCoveredByRealCollateral, _before.bob.totalDebtCoveredByRealCollateral - FV); - assertEq(_after.bob.borrowAsset.free, _before.bob.borrowAsset.free - FV); - assertEq(_after.protocolBorrowAsset.free, _before.protocolBorrowAsset.free + FV); + assertEq(_after.bob.debtAmount, _before.bob.debtAmount - FV); + assertEq(_after.bob.borrowAmount, _before.bob.borrowAmount - FV); + assertEq(_after.protocolBorrowAmount, _before.protocolBorrowAmount + FV); assertTrue(size.getLoan(loanId).repaid); } @@ -50,9 +50,9 @@ contract RepayTest is BaseTest { Vars memory _overdue = _state(); - assertEq(_overdue.bob.totalDebtCoveredByRealCollateral, _before.bob.totalDebtCoveredByRealCollateral); - assertEq(_overdue.bob.borrowAsset.free, _before.bob.borrowAsset.free); - assertEq(_overdue.protocolBorrowAsset.free, _before.protocolBorrowAsset.free); + assertEq(_overdue.bob.debtAmount, _before.bob.debtAmount); + assertEq(_overdue.bob.borrowAmount, _before.bob.borrowAmount); + assertEq(_overdue.protocolBorrowAmount, _before.protocolBorrowAmount); assertTrue(!size.getLoan(loanId).repaid); assertEq(size.getLoanStatus(loanId), LoanStatus.OVERDUE); @@ -60,9 +60,9 @@ contract RepayTest is BaseTest { Vars memory _after = _state(); - assertEq(_after.bob.totalDebtCoveredByRealCollateral, _before.bob.totalDebtCoveredByRealCollateral - FV); - assertEq(_after.bob.borrowAsset.free, _before.bob.borrowAsset.free - FV); - assertEq(_after.protocolBorrowAsset.free, _before.protocolBorrowAsset.free + FV); + assertEq(_after.bob.debtAmount, _before.bob.debtAmount - FV); + assertEq(_after.bob.borrowAmount, _before.bob.borrowAmount - FV); + assertEq(_after.protocolBorrowAmount, _before.protocolBorrowAmount + FV); assertTrue(size.getLoan(loanId).repaid); assertEq(size.getLoanStatus(loanId), LoanStatus.REPAID); } diff --git a/test/Upgrade.t.sol b/test/Upgrade.t.sol index 33663421..c5f2793d 100644 --- a/test/Upgrade.t.sol +++ b/test/Upgrade.t.sol @@ -6,7 +6,11 @@ import {Test} from "forge-std/Test.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import {Size} from "../src/Size.sol"; +import {InitializeParams} from "@src/libraries/actions/Initialize.sol"; +import {CollateralToken} from "@src/token/CollateralToken.sol"; +import {BorrowToken} from "@src/token/BorrowToken.sol"; +import {DebtToken} from "@src/token/DebtToken.sol"; +import {Size} from "@src/Size.sol"; import {SizeV2} from "./mocks/SizeV2.sol"; import {PriceFeedMock} from "./mocks/PriceFeedMock.sol"; import {WETH} from "./mocks/WETH.sol"; @@ -19,27 +23,46 @@ contract UpgradeTest is Test { PriceFeedMock public priceFeed; WETH public weth; USDC public usdc; + CollateralToken public collateralToken; + BorrowToken public borrowToken; + DebtToken public debtToken; + address public protocolVault; + address public feeRecipient; function setUp() public { priceFeed = new PriceFeedMock(address(this)); weth = new WETH(); usdc = new USDC(); + collateralToken = new CollateralToken(address(this), "Size ETH", "szETH"); + borrowToken = new BorrowToken( address(this), "Size USDC", "szUSDC"); + debtToken = new DebtToken(address(this), "Size Debt Token", "szDebt"); + protocolVault = makeAddr("protocolVault"); + feeRecipient = makeAddr("feeRecipient"); } function test_SizeUpgrade_proxy_can_be_upgraded_with_uups_casting() public { v1 = new Size(); + + InitializeParams memory params = InitializeParams( + address(this), + address(priceFeed), + address(weth), + address(usdc), + address(collateralToken), + address(borrowToken), + address(debtToken), + 1.5e4, + 1.3e4, + 0.3e4, + 0.1e4, + protocolVault, + feeRecipient + ); proxy = new ERC1967Proxy( address(v1), - abi.encodeWithSelector( - Size.initialize.selector, - address(this), - priceFeed, - address(weth), - address(usdc), - 1.5e4, - 1.3e4, - 0.3e4, - 0.1e4 + abi.encodeCall( + Size.initialize, + (params) ) ); v2 = new SizeV2(); @@ -50,18 +73,26 @@ contract UpgradeTest is Test { function test_SizeUpgrade_proxy_can_be_upgraded_directly() public { v1 = new Size(); + InitializeParams memory params = InitializeParams( + address(this), + address(priceFeed), + address(weth), + address(usdc), + address(collateralToken), + address(borrowToken), + address(debtToken), + 1.5e4, + 1.3e4, + 0.3e4, + 0.1e4, + protocolVault, + feeRecipient + ); proxy = new ERC1967Proxy( address(v1), - abi.encodeWithSelector( - Size.initialize.selector, - address(this), - priceFeed, - address(weth), - address(usdc), - 1.5e4, - 1.3e4, - 0.3e4, - 0.1e4 + abi.encodeCall( + Size.initialize, + (params) ) ); v2 = new SizeV2(); diff --git a/test/Withdraw.t.sol b/test/Withdraw.t.sol index 474045c4..3239da7e 100644 --- a/test/Withdraw.t.sol +++ b/test/Withdraw.t.sol @@ -4,25 +4,21 @@ pragma solidity 0.8.20; import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import {BaseTest} from "./BaseTest.sol"; -import {User} from "@src/libraries/UserLibrary.sol"; +import {UserView} from "@src/libraries/UserLibrary.sol"; contract WithdrawTest is BaseTest { function test_SizeWithdraw_withdraw_decreases_user_balance() public { _deposit(alice, address(usdc), 12e6); _deposit(alice, address(weth), 23e18); - User memory aliceUser = size.getUser(alice); - assertEq(aliceUser.borrowAsset.free, 12e18); - assertEq(aliceUser.borrowAsset.locked, 0); - assertEq(aliceUser.collateralAsset.free, 23e18); - assertEq(aliceUser.collateralAsset.locked, 0); + UserView memory aliceUser = size.getUserView(alice); + assertEq(aliceUser.borrowAmount, 12e18); + assertEq(aliceUser.collateralAmount, 23e18); _withdraw(alice, address(usdc), 9e6); _withdraw(alice, address(weth), 7e18); - aliceUser = size.getUser(alice); - assertEq(aliceUser.borrowAsset.free, 3e18); - assertEq(aliceUser.borrowAsset.locked, 0); - assertEq(aliceUser.collateralAsset.free, 16e18); - assertEq(aliceUser.collateralAsset.locked, 0); + aliceUser = size.getUserView(alice); + assertEq(aliceUser.borrowAmount, 3e18); + assertEq(aliceUser.collateralAmount, 16e18); } function test_SizeWithdraw_withdraw_decreases_user_balance(uint256 x, uint256 y, uint256 z, uint256 w) public { @@ -33,22 +29,18 @@ contract WithdrawTest is BaseTest { _deposit(alice, address(usdc), x * 1e6); _deposit(alice, address(weth), y * 1e18); - User memory aliceUser = size.getUser(alice); - assertEq(aliceUser.borrowAsset.free, x * 1e18); - assertEq(aliceUser.borrowAsset.locked, 0); - assertEq(aliceUser.collateralAsset.free, y * 1e18); - assertEq(aliceUser.collateralAsset.locked, 0); + UserView memory aliceUser = size.getUserView(alice); + assertEq(aliceUser.borrowAmount, x * 1e18); + assertEq(aliceUser.collateralAmount, y * 1e18); z = bound(z, 1, x); w = bound(w, 1, y); _withdraw(alice, address(usdc), z * 1e6); _withdraw(alice, address(weth), w * 1e18); - aliceUser = size.getUser(alice); - assertEq(aliceUser.borrowAsset.free, (x - z) * 1e18); - assertEq(aliceUser.borrowAsset.locked, 0); - assertEq(aliceUser.collateralAsset.free, (y - w) * 1e18); - assertEq(aliceUser.collateralAsset.locked, 0); + aliceUser = size.getUserView(alice); + assertEq(aliceUser.borrowAmount, (x - z) * 1e18); + assertEq(aliceUser.collateralAmount, (y - w) * 1e18); } function test_SizeWithdraw_deposit_withdraw_identity(uint256 valueUSDC, uint256 valueWETH) public { @@ -61,18 +53,14 @@ contract WithdrawTest is BaseTest { IERC20Metadata(usdc).approve(address(size), valueUSDC); IERC20Metadata(weth).approve(address(size), valueWETH); - assertEq(usdc.balanceOf(address(size)), 0); assertEq(usdc.balanceOf(address(alice)), valueUSDC); - assertEq(weth.balanceOf(address(size)), 0); assertEq(weth.balanceOf(address(alice)), valueWETH); size.deposit(address(usdc), valueUSDC); size.deposit(address(weth), valueWETH); assertEq(usdc.balanceOf(address(size)), valueUSDC); - assertEq(usdc.balanceOf(address(alice)), 0); assertEq(weth.balanceOf(address(size)), valueWETH); - assertEq(weth.balanceOf(address(alice)), 0); size.withdraw(address(usdc), valueUSDC); size.withdraw(address(weth), valueWETH); diff --git a/test/helpers/AssertsHelper.sol b/test/helpers/AssertsHelper.sol index 4b3b3ee2..7443f77d 100644 --- a/test/helpers/AssertsHelper.sol +++ b/test/helpers/AssertsHelper.sol @@ -2,19 +2,15 @@ pragma solidity 0.8.20; import {Test} from "forge-std/Test.sol"; -import {User} from "@src/libraries/UserLibrary.sol"; +import {UserView} from "@src/libraries/UserLibrary.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {LoanStatus} from "@src/libraries/LoanLibrary.sol"; abstract contract AssertsHelper is Test { - function assertEq(User memory a, User memory b) internal { - assertEq(a.borrowAsset.free, b.borrowAsset.free, ".borrowAsset.free"); - assertEq(a.borrowAsset.locked, b.borrowAsset.locked, ".borrowAsset.locked"); - assertEq(a.collateralAsset.free, b.collateralAsset.free, ".collateralAsset.free"); - assertEq(a.collateralAsset.locked, b.collateralAsset.locked, ".collateralAsset.locked"); - assertEq( - a.totalDebtCoveredByRealCollateral, b.totalDebtCoveredByRealCollateral, ".totalDebtCoveredByRealCollateral" - ); + function assertEq(UserView memory a, UserView memory b) internal { + assertEq(a.collateralAmount, b.collateralAmount, "collateralAmount"); + assertEq(a.borrowAmount, b.borrowAmount, "borrowAmount"); + assertEq(a.debtAmount, b.debtAmount, "debtAmount"); } function assertEq(uint256 a, uint256 b, uint256 c) internal { diff --git a/test/libraries/MathLibrary.sol b/test/libraries/MathLibrary.sol new file mode 100644 index 00000000..a47a74a6 --- /dev/null +++ b/test/libraries/MathLibrary.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {MathLibrary} from "@src/libraries/MathLibrary.sol"; + +contract MathLibraryTest is Test { + function test_MathLibrary_valueToWad_18_decimals() public { + uint256 value = 1e18; + uint256 decimals = 18; + + uint256 wad = MathLibrary.valueToWad(value, decimals); + assertEq(wad, value); + } + + function test_MathLibrary_valueToWad_18_decimals(uint256 value) public { + uint256 decimals = 18; + + uint256 wad = MathLibrary.valueToWad(value, decimals); + assertEq(wad, value); + } + + function test_MathLibrary_valueToWad_lt_18() public { + uint256 value = 1e6; + uint256 decimals = 6; + + uint256 wad = MathLibrary.valueToWad(value, decimals); + assertEq(wad, 1e18); + } + + function test_MathLibrary_valueToWad_lt_18(uint256 value) public { + value = bound(value, 0, type(uint256).max / 1e18); + uint256 decimals = 6; + + uint256 wad = MathLibrary.valueToWad(value, decimals); + assertEq(wad, value * 1e12); + } + + function test_MathLibrary_valueToWad_gt_18() public { + uint256 value = 1e24; + uint256 decimals = 24; + + vm.expectRevert(); + MathLibrary.valueToWad(value, decimals); + } + + function test_MathLibrary_valueToWad_gt_18(uint256 value) public { + uint256 decimals = 24; + + vm.expectRevert(); + MathLibrary.valueToWad(value, decimals); + } +} diff --git a/test/libraries/VaultLibrary.t.sol b/test/libraries/VaultLibrary.t.sol deleted file mode 100644 index dff82941..00000000 --- a/test/libraries/VaultLibrary.t.sol +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.20; - -import {Test} from "forge-std/Test.sol"; -import {VaultLibrary} from "@src/libraries/VaultLibrary.sol"; - -contract VaultLibraryTest is Test { - function test_VaultLibrary_valueToWad_18_decimals() public { - uint256 value = 1e18; - uint256 decimals = 18; - - uint256 wad = VaultLibrary.valueToWad(value, decimals); - assertEq(wad, value); - } - - function test_VaultLibrary_valueToWad_18_decimals(uint256 value) public { - uint256 decimals = 18; - - uint256 wad = VaultLibrary.valueToWad(value, decimals); - assertEq(wad, value); - } - - function test_VaultLibrary_valueToWad_lt_18() public { - uint256 value = 1e6; - uint256 decimals = 6; - - uint256 wad = VaultLibrary.valueToWad(value, decimals); - assertEq(wad, 1e18); - } - - function test_VaultLibrary_valueToWad_lt_18(uint256 value) public { - value = bound(value, 0, type(uint256).max / 1e18); - uint256 decimals = 6; - - uint256 wad = VaultLibrary.valueToWad(value, decimals); - assertEq(wad, value * 1e12); - } - - function test_VaultLibrary_valueToWad_gt_18() public { - uint256 value = 1e24; - uint256 decimals = 24; - - vm.expectRevert(); - VaultLibrary.valueToWad(value, decimals); - } - - function test_VaultLibrary_valueToWad_gt_18(uint256 value) public { - uint256 decimals = 24; - - vm.expectRevert(); - VaultLibrary.valueToWad(value, decimals); - } -} diff --git a/test/mocks/PriceFeedMock.sol b/test/mocks/PriceFeedMock.sol index e7bad1af..7f494250 100644 --- a/test/mocks/PriceFeedMock.sol +++ b/test/mocks/PriceFeedMock.sol @@ -6,10 +6,11 @@ import "../../src/oracle/IPriceFeed.sol"; contract PriceFeedMock is IPriceFeed, Ownable { uint256 public price; + uint8 public decimals = 18; event PriceUpdated(uint256 oldPrice, uint256 newPrice); - constructor(address owner) Ownable(owner) {} + constructor(address owner_) Ownable(owner_) {} function setPrice(uint256 newPrice) public onlyOwner { uint256 oldPrice = price;