Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/zaps/FlashLoanLooping.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
import {ISize} from "@size/src/market/interfaces/ISize.sol";
import {ISizeV1_7} from "@size/src/market/interfaces/v1.7/ISizeV1_7.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {
SellCreditMarketParams,
SellCreditMarketOnBehalfOfParams
} from "@size/src/market/libraries/actions/SellCreditMarket.sol";
import {DepositParams} from "@size/src/market/libraries/actions/Deposit.sol";
import {WithdrawParams} from "@size/src/market/libraries/actions/Withdraw.sol";
import {DexSwapUpgradeable, SwapParams} from "src/utils/DexSwapUpgradeable.sol";
import {Errors} from "@size/src/market/libraries/Errors.sol";
import {Math, PERCENT} from "@size/src/market/libraries/Math.sol";
import {Math as MathUtils} from "@openzeppelin/contracts/utils/math/Math.sol";
import {DataView} from "@size/src/market/SizeViewData.sol";
import {MorphoFlashLoanReceiverBaseUpgradeable} from "src/utils/MorphoFlashLoanReceiverBaseUpgradeable.sol";
import {MulticallUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {IFlashLoanLoopingFactory} from "src/zaps/IFlashLoanLoopingFactory.sol";
string constant FLASH_LOAN_LOOPING_VERSION = "v0.3";
struct LoopParams {
address sizeMarket;
SellCreditMarketParams[] sellCreditMarketParamsArray;
SwapParams[] swapParamsArray;
uint256 targetLeveragePercent;
}
/// @title FlashLoanLooping
/// @custom:security-contact security@size.credit
/// @author Size (https://size.credit/)
/// @notice A contract that allows users to loop using flash loans
contract FlashLoanLooping is
MulticallUpgradeable,
MorphoFlashLoanReceiverBaseUpgradeable,
DexSwapUpgradeable,
OwnableUpgradeable
{
using SafeERC20 for IERC20;
using SafeERC20 for IERC20Metadata;
// STORAGE
/// @custom:storage-location erc7201:size.storage.FlashLoanLooping
struct FlashLoanLoopingStorage {
IFlashLoanLoopingFactory _flashLoanLoopingFactory;
}
// keccak256(abi.encode(uint256(keccak256("size.storage.FlashLoanLooping")) - 1)) & ~bytes32(uint256(0xff));
// forge-lint: disable-next-line(screaming-snake-case-const)
bytes32 private constant FlashLoanLoopingStorageLocation =
0x0b46759d0375b6af79de1bfbc3b07b8359435064a6d7ded96a55feb0e2c66200;
function _getFlashLoanLoopingStorage() private pure returns (FlashLoanLoopingStorage storage $) {
assembly {
$.slot := FlashLoanLoopingStorageLocation
}
}
error InvalidPercent(uint256 percent, uint256 minPercent, uint256 maxPercent);
error TargetLeverageNotAchieved(uint256 currentLeveragePercent, uint256 targetLeveragePercent);
struct OperationParams {
address sizeMarket;
address collateralToken;
address borrowToken;
address onBehalfOf;
uint256 targetLeveragePercent;
SellCreditMarketParams[] sellCreditMarketParamsArray;
SwapParams[] swapParamsArray;
}
// CONSTRUCTOR/INITIALIZER
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(IFlashLoanLoopingFactory _flashLoanLoopingFactory, address _owner) public initializer {
if (address(_flashLoanLoopingFactory) == address(0)) {
revert Errors.NULL_ADDRESS();
}
FlashLoanLoopingStorage storage $ = _getFlashLoanLoopingStorage();
$._flashLoanLoopingFactory = _flashLoanLoopingFactory;
__Multicall_init();
__MorphoFlashLoanReceiverBase_init(address(_flashLoanLoopingFactory.morpho()));
__Ownable_init(_owner);
__DexSwapUpgradeable_init(
address(_flashLoanLoopingFactory.uniswapV2Router()),
address(_flashLoanLoopingFactory.uniswapV3Router()),
address(_flashLoanLoopingFactory.pendleRouter()),
address(_flashLoanLoopingFactory.pendleMarketFactory())
);
}
/// @dev Users must first `setAuthorization` with `SELL_CREDIT_MARKET` to this contract
function loop(LoopParams memory loopParams) external onlyOwner {
if (!flashLoanLoopingFactory().sizeFactory().isMarket(loopParams.sizeMarket)) {
revert Errors.INVALID_MARKET(loopParams.sizeMarket);
}
DataView memory dataView = ISize(loopParams.sizeMarket).data();
OperationParams memory operationParams = OperationParams({
sizeMarket: loopParams.sizeMarket,
collateralToken: address(dataView.underlyingCollateralToken),
borrowToken: address(dataView.underlyingBorrowToken),
onBehalfOf: msg.sender,
targetLeveragePercent: loopParams.targetLeveragePercent,
sellCreditMarketParamsArray: loopParams.sellCreditMarketParamsArray,
swapParamsArray: loopParams.swapParamsArray
});
uint256 flashLoanAmount = _calculateFlashLoanAmount(dataView, operationParams);
_flashLoan(address(dataView.underlyingBorrowToken), flashLoanAmount, abi.encode(operationParams));
// Deposit the remainder of the flash loaned USDC to the user
uint256 remainder = dataView.underlyingBorrowToken.balanceOf(address(this));
if (remainder > 0) {
dataView.underlyingBorrowToken.forceApprove(loopParams.sizeMarket, remainder);
ISize(loopParams.sizeMarket).deposit(
DepositParams({token: address(dataView.underlyingBorrowToken), amount: remainder, to: msg.sender})
);
}
}
function _flashLoanCallback(address, uint256, bytes memory params) internal override {
OperationParams memory operationParams = abi.decode(params, (OperationParams));
// Swap borrow token -> collateral token
_swap(operationParams.swapParamsArray);
// Approve collateral
uint256 collateralBalance = IERC20(operationParams.collateralToken).balanceOf(address(this));
IERC20(operationParams.collateralToken).forceApprove(operationParams.sizeMarket, collateralBalance);
// deposit, sell credit market, withdraw
bytes[] memory calls = new bytes[](
1 /* deposit */ + operationParams.sellCreditMarketParamsArray.length /* sell credit market */ + 1 /* withdraw */
);
calls[0] = abi.encodeCall(
ISize.deposit,
DepositParams({
token: operationParams.collateralToken,
amount: collateralBalance,
to: operationParams.onBehalfOf
})
);
for (uint256 i = 0; i < operationParams.sellCreditMarketParamsArray.length; i++) {
calls[1 + i] = abi.encodeCall(
ISizeV1_7.sellCreditMarketOnBehalfOf,
SellCreditMarketOnBehalfOfParams({
params: operationParams.sellCreditMarketParamsArray[i],
onBehalfOf: operationParams.onBehalfOf,
recipient: address(this)
})
);
}
calls[1 + operationParams.sellCreditMarketParamsArray.length] = abi.encodeCall(
ISize.withdraw,
WithdrawParams({token: operationParams.borrowToken, amount: type(uint256).max, to: address(this)})
);
// slither-disable-next-line unused-return
ISize(operationParams.sizeMarket).multicall(calls);
// Check if target leverage was achieved
uint256 leveragePercentNow = flashLoanLoopingFactory().currentLeveragePercent(
ISize(operationParams.sizeMarket), operationParams.onBehalfOf
);
if (leveragePercentNow < operationParams.targetLeveragePercent) {
revert TargetLeverageNotAchieved(leveragePercentNow, operationParams.targetLeveragePercent);
}
}
// VIEW FUNCTIONS
function _calculateFlashLoanAmount(DataView memory dataView, OperationParams memory operationParams)
internal
view
returns (uint256)
{
uint256 currentCollateral = dataView.collateralToken.balanceOf(operationParams.onBehalfOf);
uint256 currentDebt = dataView.debtToken.balanceOf(operationParams.onBehalfOf);
uint256 currentDebtInCollateral =
ISize(operationParams.sizeMarket).debtTokenAmountToCollateralTokenAmount(currentDebt);
uint256 equity = MathUtils.saturatingSub(currentCollateral, currentDebtInCollateral);
uint256 targetCollateral = Math.mulDivUp(equity, operationParams.targetLeveragePercent, PERCENT);
uint256 additionalCollateralNeeded = MathUtils.saturatingSub(targetCollateral, currentCollateral);
return flashLoanLoopingFactory().collateralTokenAmountToDebtTokenAmount(
ISize(operationParams.sizeMarket), additionalCollateralNeeded
);
}
function version() external pure returns (string memory) {
return FLASH_LOAN_LOOPING_VERSION;
}
function flashLoanLoopingFactory() public view returns (IFlashLoanLoopingFactory) {
return _getFlashLoanLoopingStorage()._flashLoanLoopingFactory;
}
}
"
},
"lib/size-solidity/src/market/interfaces/ISize.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
import {SellCreditLimitParams} from "@src/market/libraries/actions/SellCreditLimit.sol";
import {SellCreditMarketParams} from "@src/market/libraries/actions/SellCreditMarket.sol";
import {ClaimParams} from "@src/market/libraries/actions/Claim.sol";
import {BuyCreditLimitParams} from "@src/market/libraries/actions/BuyCreditLimit.sol";
import {LiquidateParams} from "@src/market/libraries/actions/Liquidate.sol";
import {DepositParams} from "@src/market/libraries/actions/Deposit.sol";
import {WithdrawParams} from "@src/market/libraries/actions/Withdraw.sol";
import {LiquidateWithReplacementParams} from "@src/market/libraries/actions/LiquidateWithReplacement.sol";
import {RepayParams} from "@src/market/libraries/actions/Repay.sol";
import {SelfLiquidateParams} from "@src/market/libraries/actions/SelfLiquidate.sol";
import {CompensateParams} from "@src/market/libraries/actions/Compensate.sol";
import {
InitializeFeeConfigParams,
InitializeOracleParams,
InitializeRiskConfigParams
} from "@src/market/libraries/actions/Initialize.sol";
import {PartialRepayParams} from "@src/market/libraries/actions/PartialRepay.sol";
import {IMulticall} from "@src/market/interfaces/IMulticall.sol";
import {ISizeView} from "@src/market/interfaces/ISizeView.sol";
import {BuyCreditMarketParams} from "@src/market/libraries/actions/BuyCreditMarket.sol";
import {SetCopyLimitOrderConfigsParams} from "@src/market/libraries/actions/SetCopyLimitOrderConfigs.sol";
import {SetUserConfigurationParams} from "@src/market/libraries/actions/SetUserConfiguration.sol";
import {ISizeAdmin} from "@src/market/interfaces/ISizeAdmin.sol";
import {ISizeV1_7} from "@src/market/interfaces/v1.7/ISizeV1_7.sol";
import {ISizeV1_8} from "@src/market/interfaces/v1.8/ISizeV1_8.sol";
string constant VERSION = "v1.8";
/// @title ISize
/// @custom:security-contact security@size.credit
/// @author Size (https://size.credit/)
/// @notice This interface is the main interface for all user-facing methods of the Size protocol
/// @dev All functions are `payable` to allow for ETH deposits in a `multicall` pattern.
/// See `Multicall.sol`
interface ISize is ISizeView, ISizeAdmin, IMulticall, ISizeV1_7, ISizeV1_8 {
/// @notice Deposit underlying borrow/collateral tokens to the protocol (e.g. USDC, WETH)
/// Borrow tokens are always deposited into the Aave Variable Pool or User Vault
/// Collateral tokens are deposited into the Size contract
/// @dev The caller must approve the transfer of the token to the protocol.
/// This function mints 1:1 Deposit Tokens (e.g. szaUSDC, szETH) in exchange of the deposited tokens
/// @param params DepositParams struct containing the following fields:
/// - address token: The address of the token to deposit
/// - uint256 amount: The amount of tokens to deposit
/// - uint256 to: The recipient of the deposit
function deposit(DepositParams calldata params) external payable;
/// @notice Withdraw underlying borrow/collateral tokens from the protocol (e.g. USDC, WETH)
/// Borrow tokens are always withdrawn from the Aave Variable Pool or User Vault
/// Collateral tokens are withdrawn from the Size contract
/// @dev This function burns 1:1 Deposit Tokens (e.g. szaUSDC, szETH) in exchange of the withdrawn tokens
/// @param params WithdrawParams struct containing the following fields:
/// - address token: The address of the token to withdraw
/// - uint256 amount: The amount of tokens to withdraw (in decimals, e.g. 1_000e6 for 1000 USDC or 10e18 for 10 WETH)
/// - uint256 to: The recipient of the withdrawal
function withdraw(WithdrawParams calldata params) external payable;
/// @notice Places a new loan offer in the orderbook
/// @param params BuyCreditLimitParams struct containing the following fields:
/// - uint256 maxDueDate: The maximum due date of the loan (e.g., 1712188800 for April 4th, 2024)
/// - YieldCurve curveRelativeTime: The yield curve for the loan offer, a struct containing the following fields:
/// - uint256[] tenors: The relative timestamps of the yield curve (for example, [30 days, 60 days, 90 days])
/// - int256[] aprs: The aprs of the yield curve (for example, [0.05e18, 0.07e18, 0.08e18] to represent 5% APR, 7% APR, and 8% APR, linear interest, respectively)
/// - uint256[] marketRateMultipliers: The market rate multipliers of the yield curve (for example, [1e18, 1.2e18, 1.3e18] to represent 100%, 120%, and 130% of the market borrow rate, respectively)
function buyCreditLimit(BuyCreditLimitParams calldata params) external payable;
/// @notice Places a new borrow offer in the orderbook
/// @param params SellCreditLimitParams struct containing the following fields:
/// - YieldCurve curveRelativeTime: The yield curve for the borrow offer, a struct containing the following fields:
/// - uint256[] tenors: The relative timestamps of the yield curve (for example, [30 days, 60 days, 90 days])
/// - int256[] aprs: The aprs of the yield curve (for example, [0.05e18, 0.07e18, 0.08e18] to represent 5% APR, 7% APR, and 8% APR, linear interest, respectively)
/// - uint256[] marketRateMultipliers: The market rate multipliers of the yield curve (for example, [0.99e18, 1e18, 1.1e18] to represent 99%, 100%, and 110% of the market borrow rate, respectively)
function sellCreditLimit(SellCreditLimitParams calldata params) external payable;
/// @notice Obtains credit via lending or buying existing credit
/// @param params BuyCreditMarketParams struct containing the following fields:
/// - address borrower: The address of the borrower (optional, for lending)
/// - uint256 creditPositionId: The id of the credit position to buy (optional, for buying credit)
/// - uint256 tenor: The tenor of the loan
/// - uint256 amount: The amount of tokens to lend or credit to buy
/// - bool exactAmountIn: Indicates if the amount is the value to be transferred or used to calculate the transfer amount
/// - uint256 deadline: The maximum timestamp for the transaction to be executed
/// - uint256 minAPR: The minimum APR the caller is willing to accept
function buyCreditMarket(BuyCreditMarketParams calldata params) external payable;
/// @notice Sells credit via borrowing or exiting an existing credit position
/// This function can be used both for selling an existing credit or to borrow by creating a DebtPosition/CreditPosition pair
/// @dev Order "takers" are the ones who pay the rounding, since "makers" are the ones passively waiting for an order to be matched
// The caller may pass type(uint256).max as the creditPositionId in order to represent "mint a new DebtPosition/CreditPosition pair"
/// @param params SellCreditMarketParams struct containing the following fields:
/// - address lender: The address of the lender
/// - uint256 creditPositionId: The id of a credit position to be sold
/// - uint256 amount: The amount of tokens to borrow (in decimals, e.g. 1_000e6 for 1000 aUSDC)
/// - uint256 tenor: The tenor of the loan
/// - uint256 deadline: The maximum timestamp for the transaction to be executed
/// - uint256 maxAPR: The maximum APR the caller is willing to accept
/// - bool exactAmountIn: this flag indicates if the amount argument represents either credit (true) or cash (false)
/// - uint256 collectionId: The collection id. If collectionId is RESERVED_ID, selects the user-defined yield curve
/// - address rateProvider: The rate provider. If collectionId is RESERVED_ID, selects the user-defined yield curve
function sellCreditMarket(SellCreditMarketParams calldata params) external payable;
/// @notice Repay a debt position by transferring the amount due of borrow tokens to the protocol, which are deposited to the Variable Pool for the lenders to claim
/// Partial repayment are currently unsupported
/// @dev The Variable Pool liquidity index is snapshotted at the time of the repayment in order to calculate the accrued interest for lenders to claim
/// @param params RepayParams struct containing the following fields:
/// - uint256 debtPositionId: The id of the debt position to repay
function repay(RepayParams calldata params) external payable;
/// @notice Claim the repayment of a loan with accrued interest from the Variable Pool
/// @dev Both ACTIVE and OVERDUE loans can't be claimed because the money is not in the protocol yet.
/// CLAIMED loans can't be claimed either because its credit has already been consumed entirely either by a previous claim or by exiting before
/// @param params ClaimParams struct containing the following fields:
/// - uint256 creditPositionId: The id of the credit position to claim
function claim(ClaimParams calldata params) external payable;
/// @notice Liquidate a debt position
/// In case of a protifable liquidation, part of the collateral remainder is split between the protocol and the liquidator
/// The split is capped by the crLiquidation parameter (otherwise, the split for overdue loans could be too much)
/// If the loan is overdue, a liquidator is charged from the borrower
/// @param params LiquidateParams struct containing the following fields:
/// - uint256 debtPositionId: The id of the debt position to liquidate
/// - uint256 minimumCollateralProfit: The minimum collateral profit that the liquidator is willing to accept from the borrower (keepers might choose to pass a value below 100% of the cash they bring and take the risk of liquidating unprofitably)
/// @return liquidatorProfitCollateralToken The amount of collateral tokens the the fee recipient received from the liquidation
function liquidate(LiquidateParams calldata params)
external
payable
returns (uint256 liquidatorProfitCollateralToken);
/// @notice Self liquidate a credit position that is undercollateralized
/// The lender cancels an amount of debt equivalent to their credit and a percentage of the protocol fees
/// @dev The user is prevented to self liquidate if a regular liquidation would be profitable
/// @param params SelfLiquidateParams struct containing the following fields:
/// - uint256 creditPositionId: The id of the credit position to self-liquidate
function selfLiquidate(SelfLiquidateParams calldata params) external payable;
/// @notice Liquidate a debt position with a replacement borrower
/// @dev This function works exactly like `liquidate`, with an added logic of replacing the borrower on the storage
/// When liquidating with replacement, nothing changes from the lenders' perspective, but a spread is created between the previous borrower rate and the new borrower rate.
/// As a result of the spread of these borrow aprs, the protocol is able to profit from the liquidation. Since the choice of the borrower impacts on the protocol's profit, this method is permissioned
/// @param params LiquidateWithReplacementParams struct containing the following fields:
/// - uint256 debtPositionId: The id of the debt position to liquidate
/// - uint256 minimumCollateralProfit: The minimum collateral profit that the liquidator is willing to accept from the borrower (keepers might choose to pass a value below 100% of the cash they bring and take the risk of liquidating unprofitably)
/// - address borrower: The address of the replacement borrower
/// - uint256 deadline: The maximum timestamp for the transaction to be executed
/// - uint256 minAPR: The minimum APR the caller is willing to accept
/// @return liquidatorProfitCollateralToken The amount of collateral tokens liquidator received from the liquidation
/// @return liquidatorProfitBorrowToken The amount of borrow tokens liquidator received from the liquidation
function liquidateWithReplacement(LiquidateWithReplacementParams calldata params)
external
payable
returns (uint256 liquidatorProfitCollateralToken, uint256 liquidatorProfitBorrowToken);
/// @notice Compensate a borrower's debt with his credit in another loan
/// The compensation can not exceed both 1) the credit the lender of `creditPositionWithDebtToRepayId` to the borrower and 2) the credit the lender of `creditPositionToCompensateId`
// @dev The caller may pass type(uint256).max as the creditPositionId in order to represent "mint a new DebtPosition/CreditPosition pair"
/// @param params CompensateParams struct containing the following fields:
/// - uint256 creditPositionWithDebtToRepayId: The id of the credit position ID with debt to repay
/// - uint256 creditPositionToCompensateId: The id of the credit position to compensate
/// - uint256 amount: The amount of tokens to compensate (in decimals, e.g. 1_000e6 for 1000 aUSDC)
function compensate(CompensateParams calldata params) external payable;
/// @notice Partial repay a debt position by selecting a specific CreditPosition
/// @param params PartialRepayParams struct containing the following fields:
/// - uint256 creditPositionWithDebtToRepayId: The id of the credit position with debt to repay
/// - uint256 amount: The amount of tokens to repay (in decimals, e.g. 1_000e6 for 1000 aUSDC)
/// - address borrower: The address of the borrower
/// @dev The partial repay amount should be less than the debt position future value
function partialRepay(PartialRepayParams calldata params) external payable;
/// @notice Set the credit positions for sale
/// @dev By default, all created creadit positions are for sale.
/// Users who want to disable the sale of all or specific credit positions can do so by calling this function.
/// By default, all users CR to open a position is crOpening. Users who want to increase their CR opening limit can do so by calling this function.
/// Note: this function was updated in v1.8 to accept a `vault` parameter.
/// Although this function is market-specific, it will change a NonTransferrableRebasingTokenVault state that will be reflected on all markets.
/// @param params SetUserConfigurationParams struct containing the following fields:
/// - address vault: The address of the user vault
/// - uint256 openingLimitBorrowCR: The opening limit borrow collateral ratio, which indicates the maximum CR the borrower is willing to accept after their offer is picked by a lender
/// - bool allCreditPositionsForSaleDisabled: This global flag indicates if all credit positions should be set for sale or not
/// - bool creditPositionIdsForSale: This flag indicates if the creditPositionIds array should be set for sale or not
/// - uint256[] creditPositionIds: The id of the credit positions
function setUserConfiguration(SetUserConfigurationParams calldata params) external payable;
/// @notice Set the copy limit order configs for a user
/// @param params SetCopyLimitOrderConfigsParams struct containing the following fields:
/// - CopyLimitOrderConfig copyLoanOfferConfig: The loan offer copy parameters
/// - uint256 minTenor: The minimum tenor of the loan offer to copy (0 means use copy yield curve tenor lower bound)
/// - uint256 maxTenor: The maximum tenor of the loan offer to copy (type(uint256).max means use copy yield curve tenor upper bound)
/// - uint256 minAPR: The minimum APR of the loan offer to copy (0 means use copy yield curve APR lower bound)
/// - uint256 maxAPR: The maximum APR of the loan offer to copy (type(uint256).max means use copy yield curve APR upper bound)
/// - int256 offsetAPR: The offset APR relative to the copied loan offer
/// - CopyLimitOrderConfig copyBorrowOfferConfig: The borrow offer copy parameters
/// - uint256 minTenor: The minimum tenor of the borrow offer to copy (0 means use copy yield curve tenor lower bound)
/// - uint256 maxTenor: The maximum tenor of the borrow offer to copy (type(uint256).max means use copy yield curve tenor upper bound)
/// - uint256 minAPR: The minimum APR of the borrow offer to copy (0 means use copy yield curve APR lower bound)
/// - uint256 maxAPR: The maximum APR of the borrow offer to copy (type(uint256).max means use copy yield curve APR upper bound)
/// - int256 offsetAPR: The offset APR relative to the copied borrow offer
/// @dev Does not erase the user's loan offer and borrow offer
/// To specify "no copy", pass a null CopyLimitOrderConfig except for offsetAPR, since a completely null CopyLimitOrderConfig
/// will default to the curator-defined CopyLimitOrderConfig for that market.
function setCopyLimitOrderConfigs(SetCopyLimitOrderConfigsParams calldata params) external payable;
}
"
},
"lib/size-solidity/src/market/interfaces/v1.7/ISizeV1_7.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
import {BuyCreditLimitOnBehalfOfParams} from "@src/market/libraries/actions/BuyCreditLimit.sol";
import {BuyCreditMarketOnBehalfOfParams} from "@src/market/libraries/actions/BuyCreditMarket.sol";
import {CompensateOnBehalfOfParams} from "@src/market/libraries/actions/Compensate.sol";
import {DepositOnBehalfOfParams} from "@src/market/libraries/actions/Deposit.sol";
import {SelfLiquidateOnBehalfOfParams} from "@src/market/libraries/actions/SelfLiquidate.sol";
import {SellCreditLimitOnBehalfOfParams} from "@src/market/libraries/actions/SellCreditLimit.sol";
import {SellCreditMarketOnBehalfOfParams} from "@src/market/libraries/actions/SellCreditMarket.sol";
import {SetCopyLimitOrderConfigsOnBehalfOfParams} from "@src/market/libraries/actions/SetCopyLimitOrderConfigs.sol";
import {SetUserConfigurationOnBehalfOfParams} from "@src/market/libraries/actions/SetUserConfiguration.sol";
import {WithdrawOnBehalfOfParams} from "@src/market/libraries/actions/Withdraw.sol";
import {ISizeFactory} from "@src/factory/interfaces/ISizeFactory.sol";
/// @title ISizeV1_7
/// @custom:security-contact security@size.credit
/// @author Size (https://size.credit/)
/// @notice The interface for the Size v1.7 authorization system
/// @dev Modifiers are moved from bare functions (e.g. `deposit`) to OnBehalfOf functions (e.g. `depositOnBehalfOf`)
interface ISizeV1_7 {
/// @notice Same as `deposit` but `onBehalfOf`
function depositOnBehalfOf(DepositOnBehalfOfParams memory params) external payable;
/// @notice Same as `withdraw` but `onBehalfOf`
function withdrawOnBehalfOf(WithdrawOnBehalfOfParams memory params) external payable;
/// @notice Same as `buyCreditLimit` but `onBehalfOf`
function buyCreditLimitOnBehalfOf(BuyCreditLimitOnBehalfOfParams memory params) external payable;
/// @notice Same as `sellCreditLimit` but `onBehalfOf`
function sellCreditLimitOnBehalfOf(SellCreditLimitOnBehalfOfParams memory params) external payable;
/// @notice Same as `buyCreditMarket` but `onBehalfOf`
/// @dev When emitting the `SwapData` event, the recipient is set as the `lender` param, which is inconsistent with the `BuyCreditMarket` event emitted just before,
/// where `lender` is passed as `onBehalfOf`. The reason is that `SwapData` emits only debt/credit recipients, while `BuyCreditMarket` emits both and also `onBehalfOf`.
function buyCreditMarketOnBehalfOf(BuyCreditMarketOnBehalfOfParams memory params) external payable;
/// @notice Same as `sellCreditMarket` but `onBehalfOf`
/// @dev When emitting the `SwapData` event, the `recipient` parameter is left out. The reason is that `SwapData` emits only debt/credit recipients,
/// while `SellCreditMarket` emits both and also the cash recipient.
function sellCreditMarketOnBehalfOf(SellCreditMarketOnBehalfOfParams memory params) external payable;
// repay is permissionless
// function repayOnBehalfOf(RepayOnBehalfOfParams memory params) external payable;
// claim is permissionless
// function claimOnBehalfOf(ClaimOnBehalfOfParams memory params) external payable;
// liquidate is permissionless
// function liquidateOnBehalfOf(LiquidateOnBehalfOfParams memory params) external payable;
/// @notice Same as `selfLiquidate` but `onBehalfOf`
function selfLiquidateOnBehalfOf(SelfLiquidateOnBehalfOfParams memory params) external payable;
// liquidateWithReplacement is permissioned
// function liquidateWithReplacementOnBehalfOf(LiquidateWithReplacementOnBehalfOfParams memory params)
// external
// payable
// returns (uint256 liquidatorProfitCollateralToken, uint256 liquidatorProfitBorrowToken);
/// @notice Same as `compensate` but `onBehalfOf`
function compensateOnBehalfOf(CompensateOnBehalfOfParams memory params) external payable;
/// partialRepay is permissionless
// function partialRepayOnBehalfOf(PartialRepayOnBehalfOfParams memory params) external payable;
/// @notice Same as `setUserConfiguration` but `onBehalfOf`
function setUserConfigurationOnBehalfOf(SetUserConfigurationOnBehalfOfParams memory params) external payable;
/// @notice Same as `setCopyLimitOrderConfigs` but `onBehalfOf`
function setCopyLimitOrderConfigsOnBehalfOf(SetCopyLimitOrderConfigsOnBehalfOfParams memory params)
external
payable;
}
"
},
"lib/size-solidity/lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
"
},
"lib/size-solidity/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC-20 standard.
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
"
},
"lib/size-solidity/lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC-20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
/**
* @dev An operation with an ERC-20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*
* NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
* only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
* set here.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
safeTransfer(token, to, value);
} else if (!token.transferAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
* has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferFromAndCallRelaxed(
IERC1363 token,
address from,
address to,
uint256 value,
bytes memory data
) internal {
if (to.code.length == 0) {
safeTransferFrom(token, from, to, value);
} else if (!token.transferFromAndCall(from, to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
* Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
* once without retrying, and relies on the returned value to be true.
*
* Reverts if the returned value is other than `true`.
*/
function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
forceApprove(token, to, value);
} else if (!token.approveAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
// bubble errors
if iszero(success) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
returnSize := returndatasize()
returnValue := mload(0)
}
if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
bool success;
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
returnSize := returndatasize()
returnValue := mload(0)
}
return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
}
}
"
},
"lib/size-solidity/src/market/libraries/actions/SellCreditMarket.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
import {CreditPosition, DebtPosition, LoanLibrary, RESERVED_ID} from "@src/market/libraries/LoanLibrary.sol";
import {Math, PERCENT} from "@src/market/libraries/Math.sol";
import {LimitOrder, OfferLibrary} from "@src/market/libraries/OfferLibrary.sol";
import {VariablePoolBorrowRateParams} from "@src/market/libraries/YieldCurveLibrary.sol";
import {State} from "@src/market/SizeStorage.sol";
import {AccountingLibrary} from "@src/market/libraries/AccountingLibrary.sol";
import {RiskLibrary} from "@src/market/libraries/RiskLibrary.sol";
import {Errors} from "@src/market/libraries/Errors.sol";
import {Events} from "@src/market/libraries/Events.sol";
import {Action} from "@src/factory/libraries/Authorization.sol";
struct SellCreditMarketParams {
// The lender
address lender;
// The credit position ID to sell
// If RESERVED_ID, a new credit position will be created
uint256 creditPositionId;
// The amount of credit to sell
uint256 amount;
// The tenor of the loan
// If creditPositionId is not RESERVED_ID, this value is ignored and the tenor of the existing loan is used
uint256 tenor;
// The deadline for the transaction
uint256 deadline;
// The maximum APR for the loan
uint256 maxAPR;
// Whether amount means credit or cash
bool exactAmountIn;
// The collection Id (introduced in v1.8)
// If collectionId is RESERVED_ID, selects the user-defined yield curve
uint256 collectionId;
// The rate provider (introduced in v1.8)
// If collectionId is RESERVED_ID, selects the user-defined yield curve
address rateProvider;
}
struct SellCreditMarketOnBehalfOfParams {
// The parameters for selling credit as a market order
SellCreditMarketParams params;
// The account to receive the debt
address onBehalfOf;
// The account to receive the cash
address recipient;
}
/// @title SellCreditMarket
/// @custom:security-contact security@size.credit
/// @author Size (https://size.credit/)
/// @notice Contains the logic for selling credit (borrowing) as a market order
library SellCreditMarket {
using OfferLibrary for LimitOrder;
using OfferLibrary for State;
using LoanLibrary for DebtPosition;
using LoanLibrary for CreditPosition;
using LoanLibrary for State;
using RiskLibrary for State;
using AccountingLibrary for State;
struct SwapDataSellCreditMarket {
CreditPosition creditPosition;
uint256 creditAmountIn;
uint256 cashAmountOut;
uint256 swapFee;
uint256 fragmentationFee;
uint256 tenor;
}
/// @notice Validates the input parameters for selling credit as a market order
/// @param state The state
/// @param externalParams The input parameters for selling credit as a market order
function validateSellCreditMarket(State storage state, SellCreditMarketOnBehalfOfParams calldata externalParams)
external
view
{
SellCreditMarketParams memory params = externalParams.params;
address onBehalfOf = externalParams.onBehalfOf;
address recipient = externalParams.recipient;
uint256 tenor;
// validate msg.sender
if (!state.data.sizeFactory.isAuthorized(msg.sender, onBehalfOf, Action.SELL_CREDIT_MARKET)) {
revert Errors.UNAUTHORIZED_ACTION(msg.sender, onBehalfOf, uint8(Action.SELL_CREDIT_MARKET));
}
// validate recipient
if (recipient == address(0)) {
revert Errors.NULL_ADDRESS();
}
// validate lender
if (params.lender == address(0)) {
revert Errors.NULL_ADDRESS();
}
// validate creditPositionId
if (params.creditPositionId == RESERVED_ID) {
tenor = params.tenor;
// validate tenor
if (tenor < state.riskConfig.minTenor || tenor > state.riskConfig.maxTenor) {
revert Errors.TENOR_OUT_OF_RANGE(tenor, state.riskConfig.minTenor, state.riskConfig.maxTenor);
}
} else {
CreditPosition storage creditPosition = state.getCreditPosition(params.creditPositionId);
DebtPosition storage debtPosition = state.getDebtPositionByCreditPositionId(params.creditPositionId);
if (onBehalfOf != creditPosition.lender) {
revert Errors.BORROWER_IS_NOT_LENDER(onBehalfOf, creditPosition.lender);
}
if (!state.isCreditPositionTransferrable(params.creditPositionId)) {
revert Errors.CREDIT_POSITION_NOT_TRANSFERRABLE(
params.creditPositionId,
uint8(state.getLoanStatus(params.creditPositionId)),
state.collateralRatio(debtPosition.borrower)
);
}
tenor = debtPosition.dueDate - block.timestamp; // positive since the credit position is transferrable, so the loan must be ACTIVE
}
// validate amount
if (params.amount == 0) {
revert Errors.NULL_AMOUNT();
}
// validate tenor
// N/A
// validate deadline
if (params.deadline < block.timestamp) {
revert Errors.PAST_DEADLINE(params.deadline);
}
// validate maxAPR
uint256 loanAPR = state.getLoanOfferAPR(params.lender, params.collectionId, params.rateProvider, tenor);
if (loanAPR > params.maxAPR) {
revert Errors.APR_GREATER_THAN_MAX_APR(loanAPR, params.maxAPR);
}
// validate exactAmountIn
// N/A
// validate inverted curve
if (!state.isLoanAPRGreaterThanBorrowOfferAPRs(params.lender, loanAPR, tenor)) {
revert Errors.INVERTED_CURVES(params.lender, tenor);
}
// validate collectionId
// validate rateProvider
// these are validated in `CollectionsManager`
}
/// @notice Returns the swap data for selling credit as a market order
/// @param state The state
/// @param params The input parameters for selling credit as a market order
/// @return swapData The swap data for selling credit as a market order
function getSwapData(State storage state, SellCreditMarketParams memory params)
public
view
returns (SwapDataSellCreditMarket memory swapData)
{
if (params.creditPositionId == RESERVED_ID) {
swapData.tenor = params.tenor;
} else {
DebtPosition storage debtPosition = state.getDebtPositionByCreditPositionId(params.creditPositionId);
swapData.creditPosition = state.getCreditPosition(params.creditPositionId);
swapData.tenor = debtPosition.dueDate - block.timestamp;
}
uint256 ratePerTenor =
state.getLoanOfferRatePerTenor(params.lender, params.collectionId, params.rateProvider, swapData.tenor);
if (params.exactAmountIn) {
swapData.creditAmountIn = params.amount;
(swapData.cashAmountOut, swapData.swapFee, swapData.fragmentationFee) = state.getCashAmountOut({
creditAmountIn: swapData.creditAmountIn,
maxCredit: params.creditPositionId == RESERVED_ID ? swapData.creditAmountIn : swapData.creditPosition.credit,
ratePerTenor: ratePerTenor,
tenor: swapData.tenor
});
} else {
swapData.cashAmountOut = params.amount;
(swapData.creditAmountIn, swapData.swapFee, swapData.fragmentationFee) = state.getCreditAmountIn({
cashAmountOut: swapData.cashAmountOut,
maxCashAmountOut: params.creditPositionId == RESERVED_ID
? swapData.cashAmountOut
: Math.mulDivDown(
swapData.creditPosition.credit,
PERCENT - state.getSwapFeePercent(swapData.tenor),
PERCENT + ratePerTenor
),
maxCredit: params.creditPositionId == RESERVED_ID
? Math.mulDivUp(
swapData.cashAmountOut, PERCENT + ratePerTenor, PERCENT - state.getSwapFeePercent(swapData.tenor)
)
: swapData.creditPosition.credit,
ratePerTenor: ratePerTenor,
tenor: swapData.tenor
});
}
}
/// @notice Executes the selling of credit as a market order
/// @param state The state
/// @param externalParams The input parameters for selling credit as a market order
function executeSellCreditMarket(State storage state, SellCreditMarketOnBehalfOfParams calldata externalParams)
external
{
SellCreditMarketParams memory params = externalParams.params;
address onBehalfOf = externalParams.onBehalfOf;
address recipient = externalParams.recipient;
emit Events.SellCreditMarket(
msg.sender,
onBehalfOf,
params.lender,
recipient,
params.creditPositionId,
params.amount,
params.tenor,
params.deadline,
params.maxAPR,
params.exactAmountIn,
params.collectionId,
params.rateProvider
);
SwapDataSellCreditMarket memory swapData = getSwapData(state, params);
if (params.creditPositionId == RESERVED_ID) {
// slither-disable-next-line unused-return
state.createDebtAndCreditPositions({
lender: onBehalfOf,
borrower: onBehalfOf,
futureValue: swapData.creditAmountIn,
dueDate: block.timestamp + swapData.tenor
});
}
uint256 exitCreditPositionId =
params.creditPositionId == RESERVED_ID ? state.data.nextCreditPositionId - 1 : params.creditPositionId;
state.createCreditPosition({
exitCreditPositionId: exitCreditPositionId,
lender: params.lender,
credit: swapData.creditAmountIn,
forSale: true
});
state.data.borrowTokenVault.transferFrom(params.lender, recipient, swapData.cashAmountOut);
state.data.borrowTokenVault.transferFrom(
params.lender, state.feeConfig.feeRecipient, swapData.swapFee + swapData.fragmentationFee
);
emit Events.SwapData(
exitCreditPositionId,
onBehalfOf,
params.lender,
swapData.creditAmountIn,
swapData.cashAmountOut + swapData.swapFee + swapData.fragmentationFee,
swapData.cashAmountOut,
swapData.swapFee,
swapData.fragmentationFee,
swapData.tenor
);
}
}
"
},
"lib/size-solidity/src/market/libraries/actions/Deposit.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IWETH} from "@src/market/interfaces/IWETH.sol";
import {State} from "@src/market/SizeStorage.sol";
import {Action} from "@src/factory/libraries/Authorization.sol";
import {Errors} from "@src/market/libraries/Errors.sol";
import {Events} from "@src/market/libraries/Events.sol";
struct DepositParams {
// The token to deposit
address token;
// The amount to deposit
uint256 amount;
// The account to deposit the tokens to
address to;
}
struct DepositOnBehalfOfParams {
// The parameters for the deposit
DepositParams params;
// The account to transfer the tokens from
address onBehalfOf;
}
/// @title Deposit
/// @custom:security-contact security@size.credit
/// @author Size (https://size.credit/)
/// @notice Contains the logic for depositing tokens into the protocol
library Deposit {
using SafeERC20 for IERC20Metadata;
using SafeERC20 for IWETH;
/// @notice Validates the deposit parameters
/// @param state The state of the protocol
/// @param externalParams The input parameters for depositing tokens
function validateDeposit(State storage state, DepositOnBehalfOfParams memory externalParams) external view {
DepositParams memory params = externalParams.params;
address onBehalfOf = externalParams.onBehalfOf;
// validate msg.sender
if (!state.data.sizeFactory.isAuthorized(msg.sender, onBehalfOf, Action.DEPOSIT)) {
revert Errors.UNAUTHORIZED_ACTION(msg.sender, onBehalfOf, uint8(Action.DEPOSIT));
}
// validate msg.value
if (
msg.value != 0
&& (msg.value != params.amount || params.token != address(state.data.weth) || onBehalfOf != msg.sender)
) {
revert Errors.INVALID_MSG_VALUE(msg.value);
}
// validate token
if (
params.token != address(state.data.underlyingCollateralToken)
&& params.token != address(state.data.underlyingBorrowToken)
) {
revert Errors.INVALID_TOKEN(params.token);
}
// validate amount
if (params.amount == 0) {
revert Errors.NULL_AMOUNT();
}
// validate to
if (params.to == address(0)) {
revert Errors.NULL_ADDRESS();
}
}
/// @notice Executes the deposit
/// @param state The state of the protocol
/// @param externalParams The input parameters for depositing tokens
/// @dev The actual deposited amount can be lower than the input amount based on the vault deposit/rounding logic
function executeDeposit(State storage state, DepositOnBehalfOfParams memory externalParams) public {
DepositParams memory params = externalParams.params;
address onBehalfOf = externalParams.onBehalfOf;
address from = onBehalfOf;
uint256 amount = params.amount;
if (msg.value > 0) {
// do not trust msg.value (see `Multicall.sol`)
amount = address(this).balance;
// slither-disable-next-line arbitrary-send-eth
state.data.weth.deposit{value: amount}();
state.data.weth.forceApprove(address(this), amount);
from = address(this);
}
if (params.token == address(state.data.underlyingBorrowToken)) {
state.data.underlyingBorrowToken.safeTransferFrom(from, address(this), amount);
state.data.underlyingBorrowToken.forceApprove(address(state.data.borrowTokenVault), amount);
amount = state.data.borrowTokenVault.deposit(params.to, amount);
} else {
state.data.underlyingCollateralToken.safeTransferFrom(from, address(this), amount);
state.data.collateralToken.mint(params.to, amount);
}
emit Events.Deposit(msg.sender, onBehalfOf, params.token, params.to, amount);
}
}
"
},
"lib/size-solidity/src/market/libraries/actions/Withdraw.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
import {State} from "@src/market/SizeStorage.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Math} from "@src/market/libraries/Math.sol";
import {Errors} from "@src/market/libraries/Errors.sol";
import {Events} from "@src/market/libraries/Events.sol";
import {Action} from "@src/factory/libraries/Authorization.sol";
import {RiskLibrary} from "@src/market/libraries/RiskLibrary.sol";
struct WithdrawParams {
// The token to withdraw
address token;
// The amount to withdraw
// The actual withdrawn amount is capped to the sender's balance
uint256 amount;
// The account to withdraw the tokens to
address to;
}
struct WithdrawOnBehalfOfParams {
// The parameters for the withdraw
WithdrawParams params;
// The account to withdraw the tokens from
address onBehalfOf;
}
/// @title Withdraw
/// @custom:security-contact security@size.credit
/// @author Size (https://size.credit/)
/// @notice Contains the logic for withdrawing tokens from the protocol
library Withdraw {
using SafeERC20 for IERC20Metadata;
using RiskLibrary for State;
/// @notice Validates the withdraw parameters
/// @param state The state of the protocol
/// @param externalParams The input parameters for withdrawing tokens
function validateWithdraw(State storage state, WithdrawOnBehalfOfParams memory externalParams) external view {
WithdrawParams memory params = externalParams.params;
address onBehalfOf = externalParams.onBehalfOf;
// validte msg.sender
if (!state.data.sizeFactory.isAuthorized(msg.sender, onBehalfOf, Action.WITHDRAW)) {
revert Errors.UNAUTHORIZED_ACTION(msg.sender, onBehalfOf, uint8(Action.WITHDRAW));
}
// validate token
if (
params.token != address(state.data.underlyingCollateralToken)
&& params.token != address(state.data.underlyingBorrowToken)
) {
revert Errors.INVALID_TOKEN(params.token);
}
// validate amount
if (params.amount == 0) {
revert Errors.NULL_AMOUNT();
}
// validate to
if (params.to == address(0)) {
revert Errors.NULL_ADDRESS();
}
}
/// @notice Executes the withdraw
/// @param state The state of the protocol
/// @param externalParams The input parameters for withdrawing tokens
/// @dev The actual withdrawn amount is capped to the sender's balance
/// The actual withdrawn amount can be lower than the requested amount based on the vault withdraw/rounding logic
function executeWithdraw(State storage state, WithdrawOnBehalfOfParams memory externalParams) public {
WithdrawParams memory params = externalParams.params;
address onBehalfOf = externalParams.onBehalfOf;
uint256 amount;
if (params.token == address(state.data.underlyingBorrowToken)) {
uint256 userBalance = state.data.borrowTokenVault.balanceOf(onBehalfOf);
amount = Math.min(params.amount, userBalance);
if (amount > 0) {
amount = (amount < userBalance)
? state.data.borrowTokenVault.withdraw(onBehalfOf, params.to, amount)
: state.data.borrowTokenVault.fullWithdraw(onBehalfOf, para
Submitted on: 2025-09-17 20:41:19
Comments
Log in to comment.
No comments yet.