Overview
ETH Balance
0 ETH
Eth Value
$0.00More Info
Private Name Tags
ContractCreator
TokenTracker
Multichain Info
No addresses found
View more zero value Internal Transactions in Advanced View mode
Advanced mode:
Loading...
Loading
Similar Match Source Code This contract matches the deployed Bytecode of the Source Code for Contract 0x53CeDa4C...261489140 The constructor portion of the code might be different and could alter the actual behaviour of the contract
Contract Name:
PurchaseBundler
Compiler Version
v0.8.24+commit.e11b9ed9
Optimization Enabled:
Yes with 20 runs
Other Settings:
cancun EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.24; import "@solmate/auth/Owned.sol"; import "@solmate/tokens/ERC721.sol"; import "@solmate/tokens/WETH.sol"; import "@solmate/utils/FixedPointMathLib.sol"; import "@solmate/utils/SafeTransferLib.sol"; import {IPool} from "@aave/interfaces/IPool.sol"; import "../../interfaces/external/IReservoir.sol"; import "../../interfaces/callbacks/IPurchaseBundler.sol"; import "../../interfaces/callbacks/ILoanCallback.sol"; import "../../interfaces/external/ICryptoPunksMarket.sol"; import "../../interfaces/external/IWrappedPunk.sol"; import "../../interfaces/external/ICryptoPunks721.sol"; import "../../interfaces/external/IUniversalRouter.sol"; import "../utils/WithProtocolFee.sol"; import "../loans/MultiSourceLoan.sol"; import "../utils/BytesLib.sol"; import "../AddressManager.sol"; import "../InputChecker.sol"; import "./TradeMarketplace.sol"; import {IAaveFlashLoanReceiver} from "../../interfaces/external/IAaveFlashLoanReceiver.sol"; import {IMultiSourceLoan as IMultiSourceLoanV2} from "@florida-v2/src/interfaces/loans/IMultiSourceLoan.sol"; import {IPermit2} from "@permit2/interfaces/IPermit2.sol"; contract PurchaseBundler is IPurchaseBundler, ILoanCallback, ERC721TokenReceiver, WithProtocolFee, TradeMarketplace, IAaveFlashLoanReceiver { using FixedPointMathLib for uint256; using BytesLib for bytes; using InputChecker for address; using SafeTransferLib for ERC20; using SafeTransferLib for WETH; uint256 public constant TAX_UPDATE_NOTICE = 7 days; /// keccak256("afterPrincipalTransfer(IMultiSourceLoan.Loan,uint256,bytes)") bytes4 private constant afterPrincipalTransferSelector = bytes4( keccak256( "afterPrincipalTransfer((address,uint256,address,address,uint256,uint256,uint256,(uint256,uint256,uint256,address,uint256,uint256,uint256)[],uint256),uint256,bytes)" ) ); /// keccak256("afterNFTTransfer(IMultiSourceLoan.Loan,bytes)") bytes4 private constant afterNFTTransferSelector = bytes4( keccak256( "afterNFTTransfer((address,uint256,address,address,uint256,uint256,uint256,(uint256,uint256,uint256,address,uint256,uint256,uint256)[],uint256),bytes)" ) ); /// keccak256("afterPrincipalTransfer(IMultiSourceLoanV2.Loan,uint256,bytes)") bytes4 private constant afterPrincipalTransferSelectorV2 = bytes4( keccak256( "afterPrincipalTransfer((address,uint256,address,address,uint256,uint256,uint256,(uint256,address,uint256,uint256,uint256,uint256)[]),uint256,bytes)" ) ); /// keccak256("afterNFTTransfer(IMultiSourceLoanV2.Loan,bytes)") bytes4 private constant afterNFTTransferSelectorV2 = bytes4( keccak256( "afterNFTTransfer((address,uint256,address,address,uint256,uint256,uint256,(uint256,address,uint256,uint256,uint256,uint256)[]),bytes)" ) ); uint256 private constant _PRECISION = 10000; uint256 private constant _MAX_TAX = 5000; uint256 private constant _MSG_SENDER_TOFFSET = 0x0; AddressManager private immutable _marketplaceContractsAddressManager; AddressManager private immutable _currencyManager; WETH private immutable _weth; Taxes private _pendingTaxes; uint256 private _pendingTaxesSetTime; Taxes private _taxes; address private _pendingMultiSourceLoanAddress; MultiSourceLoan private _multiSourceLoan; ICryptoPunksMarket private immutable _punkMarket; IWrappedPunk private immutable _wrappedPunk; ICryptoPunks721 private immutable _c721; IUniversalRouter private immutable _uniswapRouter; IPermit2 internal immutable _permit2; address private immutable _punkProxy; event BNPLLoansStarted(uint256[] loanIds); event SellAndRepayExecuted(uint256[] loanIds); event MultiSourceLoanPendingUpdate(address newAddress); event MultiSourceLoanUpdated(address newAddress); event TaxesPendingUpdate(Taxes newTaxes); event TaxesUpdated(Taxes taxes); error MarketplaceAddressNotWhitelisted(); error CurrencyNotWhitelisted(); error OnlyWethSupportedError(); error OnlyLoanCallableError(); error InvalidAddressUpdateError(); error CouldNotReturnEthError(); error InvalidTaxesError(Taxes newTaxes); error InvalidPrincipalError(); error InvalidCollateralError(); error InvalidExecutionData(); constructor( string memory name, address multiSourceLoanAddress, address marketplaceContracts, address payable wethAddress, address payable punkMarketAddress, address payable wrappedPunkAddress, address payable c721Address, address payable uniswapRouterAddress, address payable permit2Address, address currencyManager, Taxes memory taxes, uint256 minWaitTime, ProtocolFee memory protocolFee ) WithProtocolFee(tx.origin, minWaitTime, protocolFee) TradeMarketplace(name) { multiSourceLoanAddress.checkNotZero(); marketplaceContracts.checkNotZero(); _multiSourceLoan = MultiSourceLoan(multiSourceLoanAddress); _marketplaceContractsAddressManager = AddressManager(marketplaceContracts); _weth = WETH(wethAddress); _punkMarket = ICryptoPunksMarket(punkMarketAddress); _wrappedPunk = IWrappedPunk(wrappedPunkAddress); _c721 = ICryptoPunks721(c721Address); _uniswapRouter = IUniversalRouter(uniswapRouterAddress); _permit2 = IPermit2(permit2Address); _currencyManager = AddressManager(currencyManager); _wrappedPunk.registerProxy(); _punkProxy = _wrappedPunk.proxyInfo(address(this)); _taxes = taxes; _pendingTaxesSetTime = type(uint256).max; ERC20(address(_weth)).safeApprove(permit2Address, type(uint256).max); } modifier onlyLoanContract() { if (msg.sender != address(_multiSourceLoan)) { revert OnlyLoanCallableError(); } _; } function approveForSwap(address currency) external { if (!_currencyManager.isWhitelisted(currency)) { revert CurrencyNotWhitelisted(); } ERC20(currency).safeApprove(address(_permit2), type(uint256).max); } /// @inheritdoc IPurchaseBundler /// @dev Buy calls emit loan -> Before trying to transfer the NFT but after transfering the principal /// emitLoan will call the afterPrincipalTransfer Hook, which will execute the purchase. function buy(bytes[] calldata executionData) external payable returns (uint256[] memory loanIds) { loanIds = _buy(executionData); } /// @dev Similar to buy. Hook is called after the NFT transfer but before transfering WETH for repayment. /// @inheritdoc IPurchaseBundler function sell(bytes[] calldata executionData) external { _sell(executionData); } /// @dev This function is called by the buyer to execute the borrower signed sell. /// @inheritdoc IPurchaseBundler function executeSell( ERC20[] calldata currencies, uint256[] calldata currencyAmounts, ERC721[] calldata collections, uint256[] calldata tokenIds, address marketPlace, bytes[] calldata executionData ) external _storeMsgSender { if (!_marketplaceContractsAddressManager.isWhitelisted(marketPlace)) { revert MarketplaceAddressNotWhitelisted(); } if (executionData.length != collections.length) { revert InvalidExecutionData(); } if (!_marketplaceContractsAddressManager.isWhitelisted(marketPlace)) { revert MarketplaceAddressNotWhitelisted(); } address buyer = _msgSender(); for (uint256 i = 0; i < currencies.length; ++i) { if (!_currencyManager.isWhitelisted(address(currencies[i]))) { revert CurrencyNotWhitelisted(); } uint256 balance = currencies[i].balanceOf(address(this)); if (currencyAmounts[i] > balance) { currencies[i].safeTransferFrom(buyer, address(this), currencyAmounts[i] - balance); } currencies[i].safeApprove(marketPlace, currencyAmounts[i]); } for (uint256 i = 0; i < collections.length; ++i) { collections[i].setApprovalForAll(marketPlace, true); } _sell(executionData); for (uint256 i = 0; i < currencies.length; ++i) { _paybackRemaining(currencies[i]); currencies[i].safeApprove(marketPlace, 0); } for (uint256 i = 0; i < collections.length; ++i) { _givebackNFTOrPunk(collections[i], tokenIds[i]); } } /// @dev This function is called by the buyer to execute a loaned sell with eth. /// the borrower must make a listing in loan.principal /// the buyer must send eth value and it will receive the NFT without any wrapper. function executeSellWithETH( bytes calldata wethPrincipalSwapData, ERC20 principal, ERC721 collection, uint256 tokenId, bytes calldata executionData ) external payable _storeMsgSender { _weth.deposit{value: msg.value}(); _swapWETH(wethPrincipalSwapData); _sell(executionData); _givebackNFTOrPunk(collection, tokenId); _paybackRemaining(principal); _paybackRemainingWeth(); } /// @dev This function is called by the buyer to execute a loaned sell with a buy. /// the borrower must make a listing in loan.principal /// the buyer must send eth value and it will receive the NFT without any wrapper. function executeSellWithLoan(ExecuteSellWithLoanArgs calldata args) external _storeMsgSender { bytes memory params = abi.encode(args); uint256[] memory interestRateModes = new uint256[](args.borrowArgs.assets.length); for (uint256 i = 0; i < interestRateModes.length; i++) { interestRateModes[i] = 0; } args.borrowArgs.pool.flashLoan( address(this), args.borrowArgs.assets, args.borrowArgs.amounts, interestRateModes, address(this), params, 0 ); } function executeOperation( address[] calldata assets, uint256[] calldata amounts, uint256[] calldata premiums, address, bytes calldata params ) external override(IAaveFlashLoanReceiver) returns (bool) { (ExecuteSellWithLoanArgs memory args) = abi.decode(params, (ExecuteSellWithLoanArgs)); this.executeSell( args.executeSellArgs.currencies, args.executeSellArgs.currencyAmounts, args.executeSellArgs.collections, args.executeSellArgs.tokenIds, args.executeSellArgs.marketPlace, args.executeSellArgs.executionData ); _multiSourceLoan.multicall(args.loanExecutionData); address _buyer = _msgSender(); for (uint256 i = 0; i < assets.length; i++) { uint256 owed = amounts[i] + premiums[i]; ERC20 asset = ERC20(assets[i]); uint256 balance = asset.balanceOf(address(this)); if (balance < owed) { asset.safeTransferFrom(_buyer, address(this), owed - balance); } else if (balance > owed) { asset.safeTransfer(_buyer, balance - owed); } asset.safeApprove(address(args.borrowArgs.pool), owed); } return true; } /// @inheritdoc ILoanCallback /// @dev method for current MSL function afterPrincipalTransfer(IMultiSourceLoan.Loan calldata _loan, uint256 _fee, bytes calldata _executionData) external onlyLoanContract returns (bytes4) { _afterPrincipalTransfer(_loan, _fee, _executionData); return afterPrincipalTransferSelector; } /// @inheritdoc ILoanCallback /// @dev method for current MSL function afterNFTTransfer(IMultiSourceLoan.Loan calldata _loan, bytes calldata _executionData) external onlyLoanContract returns (bytes4) { _afterNFTTransfer(_loan, _executionData); return afterNFTTransferSelector; } /// @dev overloaded method for previous MSL function afterPrincipalTransfer(IMultiSourceLoanV2.Loan calldata _loan, uint256 _fee, bytes calldata _executionData) external onlyLoanContract returns (bytes4) { _weth.safeTransferFrom(_loan.borrower, address(this), _loan.principalAmount - _fee); _afterPrincipalTransfer(_map_msl(_loan), _fee, _executionData); return afterPrincipalTransferSelectorV2; } /// @dev overloaded method for previous MSL function afterNFTTransfer(IMultiSourceLoanV2.Loan calldata _loan, bytes calldata _executionData) external onlyLoanContract returns (bytes4) { _afterNFTTransfer(_map_msl(_loan), _executionData); return afterNFTTransferSelectorV2; } function _afterPrincipalTransfer(IMultiSourceLoan.Loan memory _loan, uint256 _fee, bytes calldata _executionData) private { ExecutionInfo memory purchaseBundlerExecutionInfo = abi.decode(_executionData, (ExecutionInfo)); IReservoir.ExecutionInfo memory executionInfo = purchaseBundlerExecutionInfo.reservoirExecutionInfo; if (!_marketplaceContractsAddressManager.isWhitelisted(executionInfo.module)) { revert MarketplaceAddressNotWhitelisted(); } if (_loan.principalAddress != address(_weth)) { revert OnlyWethSupportedError(); } uint256 borrowed = _loan.principalAmount - _fee; /// @dev Get WETH from the borrower and unwrap it since listings expect native ETH. _weth.withdraw(borrowed); (bool success,) = executionInfo.module.call{value: executionInfo.value}(executionInfo.data); if (!success) { revert InvalidCallbackError(); } if (borrowed > executionInfo.value) { payable(address(_loan.borrower)).transfer(borrowed - executionInfo.value); } /// @dev If contract must be owner we transfer the NFT to the purchaseBundler contract. if (executionInfo.module == address(_punkMarket)) { /// @dev Wrap punk and transfer it to the borrower (loan is in Wrapped Punks). _punkMarket.transferPunk(address(_punkProxy), _loan.nftCollateralTokenId); _wrappedPunk.mint(_loan.nftCollateralTokenId); _wrappedPunk.transferFrom(address(this), _loan.borrower, _loan.nftCollateralTokenId); } else if (purchaseBundlerExecutionInfo.contractMustBeOwner) { ERC721(_loan.nftCollateralAddress).transferFrom(address(this), _loan.borrower, _loan.nftCollateralTokenId); } _handleTax(_loan, _taxes.buyTax); } /// @dev See notes for `afterPrincipalTransfer`. function _afterNFTTransfer(IMultiSourceLoan.Loan memory loan, bytes calldata _executionData) private { ExecutionInfo memory purchaseBundlerExecutionInfo = abi.decode(_executionData, (ExecutionInfo)); IReservoir.ExecutionInfo memory executionInfo = purchaseBundlerExecutionInfo.reservoirExecutionInfo; bool contractMustBeOwner = purchaseBundlerExecutionInfo.contractMustBeOwner; if (!_marketplaceContractsAddressManager.isWhitelisted(executionInfo.module)) { revert MarketplaceAddressNotWhitelisted(); } bool success; /// @dev Similar to `afterPrincipalTransfer`, we use the matchOrder method to avoid extra transfers. /// Note that calling fullfilment on seaport will fail on this contract. if (executionInfo.module == address(_punkMarket)) { success = _sellReleasedCollateralInPunkMarket(loan, executionInfo); } else if (executionInfo.module == address(this)) { success = _sellReleasedCollateral(loan, executionInfo); } else { success = _sellReleasedCollateralInMarketplace(loan, executionInfo, contractMustBeOwner); } if (!success) { revert InvalidCallbackError(); } _handleTax(loan, _taxes.sellTax); } /// @inheritdoc IPurchaseBundler function updateMultiSourceLoanAddressFirst(address _newAddress) external onlyOwner { _newAddress.checkNotZero(); _pendingMultiSourceLoanAddress = _newAddress; emit MultiSourceLoanPendingUpdate(_newAddress); } /// @inheritdoc IPurchaseBundler function finalUpdateMultiSourceLoanAddress(address _newAddress) external onlyOwner { if (_pendingMultiSourceLoanAddress != _newAddress) { revert InvalidAddressUpdateError(); } _multiSourceLoan = MultiSourceLoan(_newAddress); _pendingMultiSourceLoanAddress = address(0); emit MultiSourceLoanUpdated(_newAddress); } /// @inheritdoc IPurchaseBundler function getMultiSourceLoanAddress() external view override returns (address) { return address(_multiSourceLoan); } /// @inheritdoc IPurchaseBundler function getTaxes() external view returns (Taxes memory) { return _taxes; } /// @inheritdoc IPurchaseBundler function getPendingTaxes() external view returns (Taxes memory) { return _pendingTaxes; } /// @inheritdoc IPurchaseBundler function getPendingTaxesSetTime() external view returns (uint256) { return _pendingTaxesSetTime; } /// @inheritdoc IPurchaseBundler function updateTaxes(Taxes calldata _newTaxes) external onlyOwner { if (_newTaxes.buyTax > _MAX_TAX || (_newTaxes.sellTax > _MAX_TAX)) { revert InvalidTaxesError(_newTaxes); } _pendingTaxes = _newTaxes; _pendingTaxesSetTime = block.timestamp; emit TaxesPendingUpdate(_newTaxes); } /// @inheritdoc IPurchaseBundler function setTaxes() external onlyOwner { if (block.timestamp < _pendingTaxesSetTime + TAX_UPDATE_NOTICE) { revert TooEarlyError(_pendingTaxesSetTime); } Taxes memory taxes = _pendingTaxes; _taxes = taxes; emit TaxesUpdated(taxes); } function _buy(bytes[] calldata executionData) private returns (uint256[] memory) { bytes[] memory encodedOutput = _multiSourceLoan.multicall(executionData); uint256[] memory loanIds = new uint256[](encodedOutput.length); uint256 total = encodedOutput.length; for (uint256 i; i < total;) { if (executionData[i].length <= 4) { // it should include the selector and parameters revert InvalidExecutionData(); } loanIds[i] = abi.decode(encodedOutput[i], (uint256)); unchecked { ++i; } } /// Return any remaining funds to sender. uint256 remainingBalance; assembly { remainingBalance := selfbalance() } if (remainingBalance != 0) { (bool success,) = payable(msg.sender).call{value: remainingBalance}(""); if (!success) { revert CouldNotReturnEthError(); } } emit BNPLLoansStarted(loanIds); return loanIds; } function _handleTax(IMultiSourceLoan.Loan memory loan, uint256 tax) private { if (tax == 0) { return; } ProtocolFee memory protocolFee = _protocolFee; address principalAddress = loan.principalAddress; address borrower = loan.borrower; uint256 totalFeeTax; uint256 totalTranches = loan.tranche.length; for (uint256 i; i < totalTranches; ++i) { IMultiSourceLoan.Tranche memory tranche = loan.tranche[i]; uint256 taxCost = tranche.principalAmount.mulDivUp(tax, _PRECISION); uint256 feeTax = taxCost.mulDivUp(protocolFee.fraction, _PRECISION); totalFeeTax += feeTax; ERC20(principalAddress).safeTransferFrom(borrower, tranche.lender, taxCost - feeTax); } if (totalFeeTax != 0) { ERC20(principalAddress).safeTransferFrom(borrower, protocolFee.recipient, totalFeeTax); } } function _paybackRemaining(ERC20 currency) private { uint256 remaining = currency.balanceOf(address(this)); if (remaining > 0) { currency.safeTransfer(_msgSender(), remaining); } } function _paybackRemainingWeth() private { uint256 remaining = _weth.balanceOf(address(this)); _weth.withdraw(remaining); payable(msg.sender).transfer(remaining); } function _givebackNFTOrPunk(ERC721 collection, uint256 tokenId) private { if (_isPunkWrapper(collection)) _givebackPunk(collection, tokenId); else _givebackNFT(collection, tokenId); } function _givebackPunk(ERC721 collection, uint256 tokenId) private { address nakedOwner = _punkMarket.punkIndexToAddress(tokenId); address buyer = _msgSender(); if (nakedOwner != address(collection) && nakedOwner != buyer) { // @dev if nakedOwner is not the wrapper, the punk has been unwrapped and should be transferred to the caller _punkMarket.transferPunk(buyer, tokenId); } else if (nakedOwner != buyer) { _givebackNFT(collection, tokenId); } } function _givebackNFT(ERC721 collection, uint256 tokenId) private { if (collection.ownerOf(tokenId) != _msgSender()) { collection.safeTransferFrom(address(this), _msgSender(), tokenId); } } function _sell(bytes[] calldata executionData) private { _multiSourceLoan.multicall(executionData); uint256[] memory loanIds = new uint256[](executionData.length); uint256 total = executionData.length; for (uint256 i = 0; i < total; ++i) { if (executionData[i].length <= 4) { // it should include the selector and parameters revert InvalidExecutionData(); } IMultiSourceLoan.LoanRepaymentData memory repaymentData = abi.decode(executionData[i][4:], (IMultiSourceLoan.LoanRepaymentData)); loanIds[i] = repaymentData.data.loanId; } emit SellAndRepayExecuted(loanIds); } function _sell(bytes calldata executionData) private { (bool success, bytes memory returndata) = address(_multiSourceLoan).call(executionData); if (!success) { revert IMulticall.MulticallFailed(0, returndata); } uint256[] memory loanIds = new uint256[](1); IMultiSourceLoan.LoanRepaymentData memory repaymentData = abi.decode(executionData[4:], (IMultiSourceLoan.LoanRepaymentData)); loanIds[0] = repaymentData.data.loanId; emit SellAndRepayExecuted(loanIds); } /// @dev Sell the NFT in the punk market before making the loan repayment /// @param loan The loan to sell the NFT from /// @param executionInfo The execution info of the market, includes calldata to execute the market sell /// @return success True if the sell was successful, false otherwise function _sellReleasedCollateralInPunkMarket( IMultiSourceLoan.Loan memory loan, IReservoir.ExecutionInfo memory executionInfo ) private returns (bool success) { /// @dev Unwrap punk ERC721 collateral = ERC721(loan.nftCollateralAddress); ERC20 principal = ERC20(loan.principalAddress); uint256 tokenId = loan.nftCollateralTokenId; uint256 owedToBorrower = executionInfo.value; collateral.transferFrom(loan.borrower, address(this), tokenId); _unwrapPunk(collateral, tokenId); /// @dev Execute sell, claim ETH from the contract and wrap it before sending it to the borrower. (success,) = executionInfo.module.call(executionInfo.data); _punkMarket.withdraw(); _weth.deposit{value: owedToBorrower}(); principal.safeTransfer(loan.borrower, owedToBorrower); } /// @dev Sell the NFT in this contract before making the loan repayment /// @param loan The loan to sell the NFT from /// @param executionInfo The execution info of the market /// @return success True if the sell was successful, false otherwise function _sellReleasedCollateral(IMultiSourceLoan.Loan memory loan, IReservoir.ExecutionInfo memory executionInfo) private returns (bool success) { ITradeMarketplace.Order memory order = abi.decode(executionInfo.data, (ITradeMarketplace.Order)); ERC721 collateral = ERC721(order.collection); ERC20 principal = ERC20(loan.principalAddress); uint256 tokenId = order.tokenId; address taker = _msgSender(); if (address(principal) != order.currency) { revert InvalidPrincipalError(); } if (address(collateral) != address(loan.nftCollateralAddress)) { revert InvalidCollateralError(); } _executeOrder(order, address(this)); if (_isPunkWrapper(collateral)) { _unwrapPunk(collateral, tokenId); _punkMarket.transferPunk(taker, tokenId); } return true; } /// @dev Sell the NFT in another market before making the loan repayment /// @param loan The loan to sell the NFT from /// @param executionInfo The execution info of the market, includes calldata to execute the market sell /// @param contractMustBeOwner If this contract must own the NFT and take the buyer's role in the market /// @return success True if the sell was successful, false otherwise function _sellReleasedCollateralInMarketplace( IMultiSourceLoan.Loan memory loan, IReservoir.ExecutionInfo memory executionInfo, bool contractMustBeOwner ) private returns (bool success) { if (!contractMustBeOwner) { (success,) = executionInfo.module.call(executionInfo.data); return success; } ERC721 collateral = ERC721(loan.nftCollateralAddress); ERC20 principal = ERC20(loan.principalAddress); uint256 tokenId = loan.nftCollateralTokenId; uint256 owedToBorrower = executionInfo.value; collateral.transferFrom(loan.borrower, address(this), tokenId); collateral.approve(executionInfo.module, tokenId); (success,) = executionInfo.module.call(executionInfo.data); principal.safeTransfer(loan.borrower, owedToBorrower); } /// @dev Function to perform a swap of eth to another currency. /// @dev The output currency is given to this contract. /// @param swapData The swap data to perform the swap. function _swapWETH(bytes calldata swapData) private { if (swapData.length == 0) return; _permit2.approve(address(_weth), address(_uniswapRouter), type(uint160).max, 0); (bytes memory commands, bytes[] memory inputs, uint256 deadline) = abi.decode(swapData, (bytes, bytes[], uint256)); _uniswapRouter.execute(commands, inputs, deadline); } /// @dev Unwraps an NFT from a wrapper contract. /// @param wrapper The wrapper contract to unwrap the NFT from. /// @param tokenId The token ID of the NFT to unwrap. function _unwrapPunk(ERC721 wrapper, uint256 tokenId) private { if (address(wrapper) == address(_c721)) { _c721.unwrapPunk(tokenId); } else if (address(wrapper) == address(_wrappedPunk)) { _wrappedPunk.burn(tokenId); } } /// @dev Unwraps an NFT from a wrapper contract. /// @param wrapper The wrapper contract to unwrap the NFT from. /// @return The naked NFT address if successful, otherwise the wrapper contract doing nothing. function _isPunkWrapper(ERC721 wrapper) private view returns (bool) { return address(wrapper) == address(_c721) || address(wrapper) == address(_wrappedPunk); } modifier _storeMsgSender() { address msgSender = _tloadMsgSender(); if (msgSender != address(0)) { _; return; } assembly { tstore(_MSG_SENDER_TOFFSET, caller()) } _; assembly { tstore(_MSG_SENDER_TOFFSET, 0) } } function _msgSender() private view returns (address msgSender) { msgSender = _tloadMsgSender(); if (msgSender == address(0)) { msgSender = msg.sender; } } function _tloadMsgSender() private view returns (address msgSender) { assembly { msgSender := tload(_MSG_SENDER_TOFFSET) } } function _map_msl(IMultiSourceLoanV2.Loan calldata _loan) private pure returns (IMultiSourceLoan.Loan memory) { uint256 totalTranches = _loan.source.length; IMultiSourceLoan.Tranche[] memory tranche = new IMultiSourceLoan.Tranche[](totalTranches); for (uint256 i = 0; i < totalTranches;) { tranche[i] = IMultiSourceLoan.Tranche( _loan.source[i].loanId, 0, _loan.source[i].principalAmount, _loan.source[i].lender, _loan.source[i].accruedInterest, _loan.source[i].startTime, _loan.source[i].aprBps ); unchecked { ++i; } } return IMultiSourceLoan.Loan( _loan.borrower, _loan.nftCollateralTokenId, _loan.nftCollateralAddress, _loan.principalAddress, _loan.principalAmount, _loan.startTime, _loan.duration, tranche, 0 ); } fallback() external payable {} receive() external payable {} }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; /// @notice Simple single owner authorization mixin. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Owned.sol) abstract contract Owned { /*////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ event OwnershipTransferred(address indexed user, address indexed newOwner); /*////////////////////////////////////////////////////////////// OWNERSHIP STORAGE //////////////////////////////////////////////////////////////*/ address public owner; modifier onlyOwner() virtual { require(msg.sender == owner, "UNAUTHORIZED"); _; } /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ constructor(address _owner) { owner = _owner; emit OwnershipTransferred(address(0), _owner); } /*////////////////////////////////////////////////////////////// OWNERSHIP LOGIC //////////////////////////////////////////////////////////////*/ function transferOwnership(address newOwner) public virtual onlyOwner { owner = newOwner; emit OwnershipTransferred(msg.sender, newOwner); } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; /// @notice Modern, minimalist, and gas efficient ERC-721 implementation. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol) abstract contract ERC721 { /*////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ event Transfer(address indexed from, address indexed to, uint256 indexed id); event Approval(address indexed owner, address indexed spender, uint256 indexed id); event ApprovalForAll(address indexed owner, address indexed operator, bool approved); /*////////////////////////////////////////////////////////////// METADATA STORAGE/LOGIC //////////////////////////////////////////////////////////////*/ string public name; string public symbol; function tokenURI(uint256 id) public view virtual returns (string memory); /*////////////////////////////////////////////////////////////// ERC721 BALANCE/OWNER STORAGE //////////////////////////////////////////////////////////////*/ mapping(uint256 => address) internal _ownerOf; mapping(address => uint256) internal _balanceOf; function ownerOf(uint256 id) public view virtual returns (address owner) { require((owner = _ownerOf[id]) != address(0), "NOT_MINTED"); } function balanceOf(address owner) public view virtual returns (uint256) { require(owner != address(0), "ZERO_ADDRESS"); return _balanceOf[owner]; } /*////////////////////////////////////////////////////////////// ERC721 APPROVAL STORAGE //////////////////////////////////////////////////////////////*/ mapping(uint256 => address) public getApproved; mapping(address => mapping(address => bool)) public isApprovedForAll; /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ constructor(string memory _name, string memory _symbol) { name = _name; symbol = _symbol; } /*////////////////////////////////////////////////////////////// ERC721 LOGIC //////////////////////////////////////////////////////////////*/ function approve(address spender, uint256 id) public virtual { address owner = _ownerOf[id]; require(msg.sender == owner || isApprovedForAll[owner][msg.sender], "NOT_AUTHORIZED"); getApproved[id] = spender; emit Approval(owner, spender, id); } function setApprovalForAll(address operator, bool approved) public virtual { isApprovedForAll[msg.sender][operator] = approved; emit ApprovalForAll(msg.sender, operator, approved); } function transferFrom( address from, address to, uint256 id ) public virtual { require(from == _ownerOf[id], "WRONG_FROM"); require(to != address(0), "INVALID_RECIPIENT"); require( msg.sender == from || isApprovedForAll[from][msg.sender] || msg.sender == getApproved[id], "NOT_AUTHORIZED" ); // Underflow of the sender's balance is impossible because we check for // ownership above and the recipient's balance can't realistically overflow. unchecked { _balanceOf[from]--; _balanceOf[to]++; } _ownerOf[id] = to; delete getApproved[id]; emit Transfer(from, to, id); } function safeTransferFrom( address from, address to, uint256 id ) public virtual { transferFrom(from, to, id); require( to.code.length == 0 || ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, "") == ERC721TokenReceiver.onERC721Received.selector, "UNSAFE_RECIPIENT" ); } function safeTransferFrom( address from, address to, uint256 id, bytes calldata data ) public virtual { transferFrom(from, to, id); require( to.code.length == 0 || ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, data) == ERC721TokenReceiver.onERC721Received.selector, "UNSAFE_RECIPIENT" ); } /*////////////////////////////////////////////////////////////// ERC165 LOGIC //////////////////////////////////////////////////////////////*/ function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { return interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165 interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721 interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata } /*////////////////////////////////////////////////////////////// INTERNAL MINT/BURN LOGIC //////////////////////////////////////////////////////////////*/ function _mint(address to, uint256 id) internal virtual { require(to != address(0), "INVALID_RECIPIENT"); require(_ownerOf[id] == address(0), "ALREADY_MINTED"); // Counter overflow is incredibly unrealistic. unchecked { _balanceOf[to]++; } _ownerOf[id] = to; emit Transfer(address(0), to, id); } function _burn(uint256 id) internal virtual { address owner = _ownerOf[id]; require(owner != address(0), "NOT_MINTED"); // Ownership check above ensures no underflow. unchecked { _balanceOf[owner]--; } delete _ownerOf[id]; delete getApproved[id]; emit Transfer(owner, address(0), id); } /*////////////////////////////////////////////////////////////// INTERNAL SAFE MINT LOGIC //////////////////////////////////////////////////////////////*/ function _safeMint(address to, uint256 id) internal virtual { _mint(to, id); require( to.code.length == 0 || ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, "") == ERC721TokenReceiver.onERC721Received.selector, "UNSAFE_RECIPIENT" ); } function _safeMint( address to, uint256 id, bytes memory data ) internal virtual { _mint(to, id); require( to.code.length == 0 || ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, data) == ERC721TokenReceiver.onERC721Received.selector, "UNSAFE_RECIPIENT" ); } } /// @notice A generic interface for a contract which properly accepts ERC721 tokens. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol) abstract contract ERC721TokenReceiver { function onERC721Received( address, address, uint256, bytes calldata ) external virtual returns (bytes4) { return ERC721TokenReceiver.onERC721Received.selector; } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; import {ERC20} from "./ERC20.sol"; import {SafeTransferLib} from "../utils/SafeTransferLib.sol"; /// @notice Minimalist and modern Wrapped Ether implementation. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/WETH.sol) /// @author Inspired by WETH9 (https://github.com/dapphub/ds-weth/blob/master/src/weth9.sol) contract WETH is ERC20("Wrapped Ether", "WETH", 18) { using SafeTransferLib for address; event Deposit(address indexed from, uint256 amount); event Withdrawal(address indexed to, uint256 amount); function deposit() public payable virtual { _mint(msg.sender, msg.value); emit Deposit(msg.sender, msg.value); } function withdraw(uint256 amount) public virtual { _burn(msg.sender, amount); emit Withdrawal(msg.sender, amount); msg.sender.safeTransferETH(amount); } receive() external payable virtual { deposit(); } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; /// @notice Arithmetic library with operations for fixed-point numbers. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol) /// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol) library FixedPointMathLib { /*////////////////////////////////////////////////////////////// SIMPLIFIED FIXED POINT OPERATIONS //////////////////////////////////////////////////////////////*/ uint256 internal constant MAX_UINT256 = 2**256 - 1; uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s. function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) { return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down. } function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) { return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up. } function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) { return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down. } function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) { return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up. } /*////////////////////////////////////////////////////////////// LOW LEVEL FIXED POINT OPERATIONS //////////////////////////////////////////////////////////////*/ function mulDivDown( uint256 x, uint256 y, uint256 denominator ) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y)) if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) { revert(0, 0) } // Divide x * y by the denominator. z := div(mul(x, y), denominator) } } function mulDivUp( uint256 x, uint256 y, uint256 denominator ) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y)) if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) { revert(0, 0) } // If x * y modulo the denominator is strictly greater than 0, // 1 is added to round up the division of x * y by the denominator. z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator)) } } function rpow( uint256 x, uint256 n, uint256 scalar ) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { switch x case 0 { switch n case 0 { // 0 ** 0 = 1 z := scalar } default { // 0 ** n = 0 z := 0 } } default { switch mod(n, 2) case 0 { // If n is even, store scalar in z for now. z := scalar } default { // If n is odd, store x in z for now. z := x } // Shifting right by 1 is like dividing by 2. let half := shr(1, scalar) for { // Shift n right by 1 before looping to halve it. n := shr(1, n) } n { // Shift n right by 1 each iteration to halve it. n := shr(1, n) } { // Revert immediately if x ** 2 would overflow. // Equivalent to iszero(eq(div(xx, x), x)) here. if shr(128, x) { revert(0, 0) } // Store x squared. let xx := mul(x, x) // Round to the nearest number. let xxRound := add(xx, half) // Revert if xx + half overflowed. if lt(xxRound, xx) { revert(0, 0) } // Set x to scaled xxRound. x := div(xxRound, scalar) // If n is even: if mod(n, 2) { // Compute z * x. let zx := mul(z, x) // If z * x overflowed: if iszero(eq(div(zx, x), z)) { // Revert if x is non-zero. if iszero(iszero(x)) { revert(0, 0) } } // Round to the nearest number. let zxRound := add(zx, half) // Revert if zx + half overflowed. if lt(zxRound, zx) { revert(0, 0) } // Return properly scaled zxRound. z := div(zxRound, scalar) } } } } } /*////////////////////////////////////////////////////////////// GENERAL NUMBER UTILITIES //////////////////////////////////////////////////////////////*/ function sqrt(uint256 x) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { let y := x // We start y at x, which will help us make our initial estimate. z := 181 // The "correct" value is 1, but this saves a multiplication later. // This segment is to get a reasonable initial estimate for the Babylonian method. With a bad // start, the correct # of bits increases ~linearly each iteration instead of ~quadratically. // We check y >= 2^(k + 8) but shift right by k bits // each branch to ensure that if x >= 256, then y >= 256. if iszero(lt(y, 0x10000000000000000000000000000000000)) { y := shr(128, y) z := shl(64, z) } if iszero(lt(y, 0x1000000000000000000)) { y := shr(64, y) z := shl(32, z) } if iszero(lt(y, 0x10000000000)) { y := shr(32, y) z := shl(16, z) } if iszero(lt(y, 0x1000000)) { y := shr(16, y) z := shl(8, z) } // Goal was to get z*z*y within a small factor of x. More iterations could // get y in a tighter range. Currently, we will have y in [256, 256*2^16). // We ensured y >= 256 so that the relative difference between y and y+1 is small. // That's not possible if x < 256 but we can just verify those cases exhaustively. // Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256. // Correctness can be checked exhaustively for x < 256, so we assume y >= 256. // Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps. // For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range // (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256. // Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate // sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18. // There is no overflow risk here since y < 2^136 after the first branch above. z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181. // Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough. z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) // If x+1 is a perfect square, the Babylonian method cycles between // floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor. // See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division // Since the ceil is rare, we save gas on the assignment and repeat division in the rare case. // If you don't care whether the floor or ceil square root is returned, you can remove this statement. z := sub(z, lt(div(x, z), z)) } } function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { // Mod x by y. Note this will return // 0 instead of reverting if y is zero. z := mod(x, y) } } function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) { /// @solidity memory-safe-assembly assembly { // Divide x by y. Note this will return // 0 instead of reverting if y is zero. r := div(x, y) } } function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { // Add 1 to x * y if x % y > 0. Note this will // return 0 instead of reverting if y is zero. z := add(gt(mod(x, y), 0), div(x, y)) } } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; import {ERC20} from "../tokens/ERC20.sol"; /// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol) /// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer. /// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller. library SafeTransferLib { /*////////////////////////////////////////////////////////////// ETH OPERATIONS //////////////////////////////////////////////////////////////*/ function safeTransferETH(address to, uint256 amount) internal { bool success; /// @solidity memory-safe-assembly assembly { // Transfer the ETH and store if it succeeded or not. success := call(gas(), to, amount, 0, 0, 0, 0) } require(success, "ETH_TRANSFER_FAILED"); } /*////////////////////////////////////////////////////////////// ERC20 OPERATIONS //////////////////////////////////////////////////////////////*/ function safeTransferFrom( ERC20 token, address from, address to, uint256 amount ) internal { bool success; /// @solidity memory-safe-assembly assembly { // Get a pointer to some free memory. let freeMemoryPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000) mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from" argument. mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument. mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type. success := and( // Set success to whether the call reverted, if not we check it either // returned exactly 1 (can't just be non-zero data), or had no return data. or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3. // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. // Counterintuitively, this call must be positioned second to the or() call in the // surrounding and() call or else returndatasize() will be zero during the computation. call(gas(), token, 0, freeMemoryPointer, 100, 0, 32) ) } require(success, "TRANSFER_FROM_FAILED"); } function safeTransfer( ERC20 token, address to, uint256 amount ) internal { bool success; /// @solidity memory-safe-assembly assembly { // Get a pointer to some free memory. let freeMemoryPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000) mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument. mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type. success := and( // Set success to whether the call reverted, if not we check it either // returned exactly 1 (can't just be non-zero data), or had no return data. or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2. // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. // Counterintuitively, this call must be positioned second to the or() call in the // surrounding and() call or else returndatasize() will be zero during the computation. call(gas(), token, 0, freeMemoryPointer, 68, 0, 32) ) } require(success, "TRANSFER_FAILED"); } function safeApprove( ERC20 token, address to, uint256 amount ) internal { bool success; /// @solidity memory-safe-assembly assembly { // Get a pointer to some free memory. let freeMemoryPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000) mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument. mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type. success := and( // Set success to whether the call reverted, if not we check it either // returned exactly 1 (can't just be non-zero data), or had no return data. or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2. // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. // Counterintuitively, this call must be positioned second to the or() call in the // surrounding and() call or else returndatasize() will be zero during the computation. call(gas(), token, 0, freeMemoryPointer, 68, 0, 32) ) } require(success, "APPROVE_FAILED"); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {IPoolAddressesProvider} from './IPoolAddressesProvider.sol'; import {DataTypes} from '../protocol/libraries/types/DataTypes.sol'; /** * @title IPool * @author Aave * @notice Defines the basic interface for an Aave Pool. */ interface IPool { /** * @dev Emitted on mintUnbacked() * @param reserve The address of the underlying asset of the reserve * @param user The address initiating the supply * @param onBehalfOf The beneficiary of the supplied assets, receiving the aTokens * @param amount The amount of supplied assets * @param referralCode The referral code used */ event MintUnbacked( address indexed reserve, address user, address indexed onBehalfOf, uint256 amount, uint16 indexed referralCode ); /** * @dev Emitted on backUnbacked() * @param reserve The address of the underlying asset of the reserve * @param backer The address paying for the backing * @param amount The amount added as backing * @param fee The amount paid in fees */ event BackUnbacked(address indexed reserve, address indexed backer, uint256 amount, uint256 fee); /** * @dev Emitted on supply() * @param reserve The address of the underlying asset of the reserve * @param user The address initiating the supply * @param onBehalfOf The beneficiary of the supply, receiving the aTokens * @param amount The amount supplied * @param referralCode The referral code used */ event Supply( address indexed reserve, address user, address indexed onBehalfOf, uint256 amount, uint16 indexed referralCode ); /** * @dev Emitted on withdraw() * @param reserve The address of the underlying asset being withdrawn * @param user The address initiating the withdrawal, owner of aTokens * @param to The address that will receive the underlying * @param amount The amount to be withdrawn */ event Withdraw(address indexed reserve, address indexed user, address indexed to, uint256 amount); /** * @dev Emitted on borrow() and flashLoan() when debt needs to be opened * @param reserve The address of the underlying asset being borrowed * @param user The address of the user initiating the borrow(), receiving the funds on borrow() or just * initiator of the transaction on flashLoan() * @param onBehalfOf The address that will be getting the debt * @param amount The amount borrowed out * @param interestRateMode The rate mode: 2 for Variable, 1 is deprecated (changed on v3.2.0) * @param borrowRate The numeric rate at which the user has borrowed, expressed in ray * @param referralCode The referral code used */ event Borrow( address indexed reserve, address user, address indexed onBehalfOf, uint256 amount, DataTypes.InterestRateMode interestRateMode, uint256 borrowRate, uint16 indexed referralCode ); /** * @dev Emitted on repay() * @param reserve The address of the underlying asset of the reserve * @param user The beneficiary of the repayment, getting his debt reduced * @param repayer The address of the user initiating the repay(), providing the funds * @param amount The amount repaid * @param useATokens True if the repayment is done using aTokens, `false` if done with underlying asset directly */ event Repay( address indexed reserve, address indexed user, address indexed repayer, uint256 amount, bool useATokens ); /** * @dev Emitted on borrow(), repay() and liquidationCall() when using isolated assets * @param asset The address of the underlying asset of the reserve * @param totalDebt The total isolation mode debt for the reserve */ event IsolationModeTotalDebtUpdated(address indexed asset, uint256 totalDebt); /** * @dev Emitted when the user selects a certain asset category for eMode * @param user The address of the user * @param categoryId The category id */ event UserEModeSet(address indexed user, uint8 categoryId); /** * @dev Emitted on setUserUseReserveAsCollateral() * @param reserve The address of the underlying asset of the reserve * @param user The address of the user enabling the usage as collateral */ event ReserveUsedAsCollateralEnabled(address indexed reserve, address indexed user); /** * @dev Emitted on setUserUseReserveAsCollateral() * @param reserve The address of the underlying asset of the reserve * @param user The address of the user enabling the usage as collateral */ event ReserveUsedAsCollateralDisabled(address indexed reserve, address indexed user); /** * @dev Emitted on flashLoan() * @param target The address of the flash loan receiver contract * @param initiator The address initiating the flash loan * @param asset The address of the asset being flash borrowed * @param amount The amount flash borrowed * @param interestRateMode The flashloan mode: 0 for regular flashloan, * 1 for Stable (Deprecated on v3.2.0), 2 for Variable * @param premium The fee flash borrowed * @param referralCode The referral code used */ event FlashLoan( address indexed target, address initiator, address indexed asset, uint256 amount, DataTypes.InterestRateMode interestRateMode, uint256 premium, uint16 indexed referralCode ); /** * @dev Emitted when a borrower is liquidated. * @param collateralAsset The address of the underlying asset used as collateral, to receive as result of the liquidation * @param debtAsset The address of the underlying borrowed asset to be repaid with the liquidation * @param user The address of the borrower getting liquidated * @param debtToCover The debt amount of borrowed `asset` the liquidator wants to cover * @param liquidatedCollateralAmount The amount of collateral received by the liquidator * @param liquidator The address of the liquidator * @param receiveAToken True if the liquidators wants to receive the collateral aTokens, `false` if he wants * to receive the underlying collateral asset directly */ event LiquidationCall( address indexed collateralAsset, address indexed debtAsset, address indexed user, uint256 debtToCover, uint256 liquidatedCollateralAmount, address liquidator, bool receiveAToken ); /** * @dev Emitted when the state of a reserve is updated. * @param reserve The address of the underlying asset of the reserve * @param liquidityRate The next liquidity rate * @param stableBorrowRate The next stable borrow rate @note deprecated on v3.2.0 * @param variableBorrowRate The next variable borrow rate * @param liquidityIndex The next liquidity index * @param variableBorrowIndex The next variable borrow index */ event ReserveDataUpdated( address indexed reserve, uint256 liquidityRate, uint256 stableBorrowRate, uint256 variableBorrowRate, uint256 liquidityIndex, uint256 variableBorrowIndex ); /** * @dev Emitted when the protocol treasury receives minted aTokens from the accrued interest. * @param reserve The address of the reserve * @param amountMinted The amount minted to the treasury */ event MintedToTreasury(address indexed reserve, uint256 amountMinted); /** * @notice Mints an `amount` of aTokens to the `onBehalfOf` * @param asset The address of the underlying asset to mint * @param amount The amount to mint * @param onBehalfOf The address that will receive the aTokens * @param referralCode Code used to register the integrator originating the operation, for potential rewards. * 0 if the action is executed directly by the user, without any middle-man */ function mintUnbacked( address asset, uint256 amount, address onBehalfOf, uint16 referralCode ) external; /** * @notice Back the current unbacked underlying with `amount` and pay `fee`. * @param asset The address of the underlying asset to back * @param amount The amount to back * @param fee The amount paid in fees * @return The backed amount */ function backUnbacked(address asset, uint256 amount, uint256 fee) external returns (uint256); /** * @notice Supplies an `amount` of underlying asset into the reserve, receiving in return overlying aTokens. * - E.g. User supplies 100 USDC and gets in return 100 aUSDC * @param asset The address of the underlying asset to supply * @param amount The amount to be supplied * @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user * wants to receive them on his own wallet, or a different address if the beneficiary of aTokens * is a different wallet * @param referralCode Code used to register the integrator originating the operation, for potential rewards. * 0 if the action is executed directly by the user, without any middle-man */ function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external; /** * @notice Supply with transfer approval of asset to be supplied done via permit function * see: https://eips.ethereum.org/EIPS/eip-2612 and https://eips.ethereum.org/EIPS/eip-713 * @param asset The address of the underlying asset to supply * @param amount The amount to be supplied * @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user * wants to receive them on his own wallet, or a different address if the beneficiary of aTokens * is a different wallet * @param deadline The deadline timestamp that the permit is valid * @param referralCode Code used to register the integrator originating the operation, for potential rewards. * 0 if the action is executed directly by the user, without any middle-man * @param permitV The V parameter of ERC712 permit sig * @param permitR The R parameter of ERC712 permit sig * @param permitS The S parameter of ERC712 permit sig */ function supplyWithPermit( address asset, uint256 amount, address onBehalfOf, uint16 referralCode, uint256 deadline, uint8 permitV, bytes32 permitR, bytes32 permitS ) external; /** * @notice Withdraws an `amount` of underlying asset from the reserve, burning the equivalent aTokens owned * E.g. User has 100 aUSDC, calls withdraw() and receives 100 USDC, burning the 100 aUSDC * @param asset The address of the underlying asset to withdraw * @param amount The underlying amount to be withdrawn * - Send the value type(uint256).max in order to withdraw the whole aToken balance * @param to The address that will receive the underlying, same as msg.sender if the user * wants to receive it on his own wallet, or a different address if the beneficiary is a * different wallet * @return The final amount withdrawn */ function withdraw(address asset, uint256 amount, address to) external returns (uint256); /** * @notice Allows users to borrow a specific `amount` of the reserve underlying asset, provided that the borrower * already supplied enough collateral, or he was given enough allowance by a credit delegator on the VariableDebtToken * - E.g. User borrows 100 USDC passing as `onBehalfOf` his own address, receiving the 100 USDC in his wallet * and 100 variable debt tokens * @param asset The address of the underlying asset to borrow * @param amount The amount to be borrowed * @param interestRateMode 2 for Variable, 1 is deprecated on v3.2.0 * @param referralCode The code used to register the integrator originating the operation, for potential rewards. * 0 if the action is executed directly by the user, without any middle-man * @param onBehalfOf The address of the user who will receive the debt. Should be the address of the borrower itself * calling the function if he wants to borrow against his own collateral, or the address of the credit delegator * if he has been given credit delegation allowance */ function borrow( address asset, uint256 amount, uint256 interestRateMode, uint16 referralCode, address onBehalfOf ) external; /** * @notice Repays a borrowed `amount` on a specific reserve, burning the equivalent debt tokens owned * - E.g. User repays 100 USDC, burning 100 variable debt tokens of the `onBehalfOf` address * @param asset The address of the borrowed underlying asset previously borrowed * @param amount The amount to repay * - Send the value type(uint256).max in order to repay the whole debt for `asset` on the specific `debtMode` * @param interestRateMode 2 for Variable, 1 is deprecated on v3.2.0 * @param onBehalfOf The address of the user who will get his debt reduced/removed. Should be the address of the * user calling the function if he wants to reduce/remove his own debt, or the address of any other * other borrower whose debt should be removed * @return The final amount repaid */ function repay( address asset, uint256 amount, uint256 interestRateMode, address onBehalfOf ) external returns (uint256); /** * @notice Repay with transfer approval of asset to be repaid done via permit function * see: https://eips.ethereum.org/EIPS/eip-2612 and https://eips.ethereum.org/EIPS/eip-713 * @param asset The address of the borrowed underlying asset previously borrowed * @param amount The amount to repay * - Send the value type(uint256).max in order to repay the whole debt for `asset` on the specific `debtMode` * @param interestRateMode 2 for Variable, 1 is deprecated on v3.2.0 * @param onBehalfOf Address of the user who will get his debt reduced/removed. Should be the address of the * user calling the function if he wants to reduce/remove his own debt, or the address of any other * other borrower whose debt should be removed * @param deadline The deadline timestamp that the permit is valid * @param permitV The V parameter of ERC712 permit sig * @param permitR The R parameter of ERC712 permit sig * @param permitS The S parameter of ERC712 permit sig * @return The final amount repaid */ function repayWithPermit( address asset, uint256 amount, uint256 interestRateMode, address onBehalfOf, uint256 deadline, uint8 permitV, bytes32 permitR, bytes32 permitS ) external returns (uint256); /** * @notice Repays a borrowed `amount` on a specific reserve using the reserve aTokens, burning the * equivalent debt tokens * - E.g. User repays 100 USDC using 100 aUSDC, burning 100 variable debt tokens * @dev Passing uint256.max as amount will clean up any residual aToken dust balance, if the user aToken * balance is not enough to cover the whole debt * @param asset The address of the borrowed underlying asset previously borrowed * @param amount The amount to repay * - Send the value type(uint256).max in order to repay the whole debt for `asset` on the specific `debtMode` * @param interestRateMode DEPRECATED in v3.2.0 * @return The final amount repaid */ function repayWithATokens( address asset, uint256 amount, uint256 interestRateMode ) external returns (uint256); /** * @notice Allows suppliers to enable/disable a specific supplied asset as collateral * @param asset The address of the underlying asset supplied * @param useAsCollateral True if the user wants to use the supply as collateral, false otherwise */ function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) external; /** * @notice Function to liquidate a non-healthy position collateral-wise, with Health Factor below 1 * - The caller (liquidator) covers `debtToCover` amount of debt of the user getting liquidated, and receives * a proportionally amount of the `collateralAsset` plus a bonus to cover market risk * @param collateralAsset The address of the underlying asset used as collateral, to receive as result of the liquidation * @param debtAsset The address of the underlying borrowed asset to be repaid with the liquidation * @param user The address of the borrower getting liquidated * @param debtToCover The debt amount of borrowed `asset` the liquidator wants to cover * @param receiveAToken True if the liquidators wants to receive the collateral aTokens, `false` if he wants * to receive the underlying collateral asset directly */ function liquidationCall( address collateralAsset, address debtAsset, address user, uint256 debtToCover, bool receiveAToken ) external; /** * @notice Allows smartcontracts to access the liquidity of the pool within one transaction, * as long as the amount taken plus a fee is returned. * @dev IMPORTANT There are security concerns for developers of flashloan receiver contracts that must be kept * into consideration. For further details please visit https://docs.aave.com/developers/ * @param receiverAddress The address of the contract receiving the funds, implementing IFlashLoanReceiver interface * @param assets The addresses of the assets being flash-borrowed * @param amounts The amounts of the assets being flash-borrowed * @param interestRateModes Types of the debt to open if the flash loan is not returned: * 0 -> Don't open any debt, just revert if funds can't be transferred from the receiver * 1 -> Deprecated on v3.2.0 * 2 -> Open debt at variable rate for the value of the amount flash-borrowed to the `onBehalfOf` address * @param onBehalfOf The address that will receive the debt in the case of using 2 on `modes` * @param params Variadic packed params to pass to the receiver as extra information * @param referralCode The code used to register the integrator originating the operation, for potential rewards. * 0 if the action is executed directly by the user, without any middle-man */ function flashLoan( address receiverAddress, address[] calldata assets, uint256[] calldata amounts, uint256[] calldata interestRateModes, address onBehalfOf, bytes calldata params, uint16 referralCode ) external; /** * @notice Allows smartcontracts to access the liquidity of the pool within one transaction, * as long as the amount taken plus a fee is returned. * @dev IMPORTANT There are security concerns for developers of flashloan receiver contracts that must be kept * into consideration. For further details please visit https://docs.aave.com/developers/ * @param receiverAddress The address of the contract receiving the funds, implementing IFlashLoanSimpleReceiver interface * @param asset The address of the asset being flash-borrowed * @param amount The amount of the asset being flash-borrowed * @param params Variadic packed params to pass to the receiver as extra information * @param referralCode The code used to register the integrator originating the operation, for potential rewards. * 0 if the action is executed directly by the user, without any middle-man */ function flashLoanSimple( address receiverAddress, address asset, uint256 amount, bytes calldata params, uint16 referralCode ) external; /** * @notice Returns the user account data across all the reserves * @param user The address of the user * @return totalCollateralBase The total collateral of the user in the base currency used by the price feed * @return totalDebtBase The total debt of the user in the base currency used by the price feed * @return availableBorrowsBase The borrowing power left of the user in the base currency used by the price feed * @return currentLiquidationThreshold The liquidation threshold of the user * @return ltv The loan to value of The user * @return healthFactor The current health factor of the user */ function getUserAccountData( address user ) external view returns ( uint256 totalCollateralBase, uint256 totalDebtBase, uint256 availableBorrowsBase, uint256 currentLiquidationThreshold, uint256 ltv, uint256 healthFactor ); /** * @notice Initializes a reserve, activating it, assigning an aToken and debt tokens and an * interest rate strategy * @dev Only callable by the PoolConfigurator contract * @param asset The address of the underlying asset of the reserve * @param aTokenAddress The address of the aToken that will be assigned to the reserve * @param variableDebtAddress The address of the VariableDebtToken that will be assigned to the reserve * @param interestRateStrategyAddress The address of the interest rate strategy contract */ function initReserve( address asset, address aTokenAddress, address variableDebtAddress, address interestRateStrategyAddress ) external; /** * @notice Drop a reserve * @dev Only callable by the PoolConfigurator contract * @dev Does not reset eMode flags, which must be considered when reusing the same reserve id for a different reserve. * @param asset The address of the underlying asset of the reserve */ function dropReserve(address asset) external; /** * @notice Updates the address of the interest rate strategy contract * @dev Only callable by the PoolConfigurator contract * @param asset The address of the underlying asset of the reserve * @param rateStrategyAddress The address of the interest rate strategy contract */ function setReserveInterestRateStrategyAddress( address asset, address rateStrategyAddress ) external; /** * @notice Accumulates interest to all indexes of the reserve * @dev Only callable by the PoolConfigurator contract * @dev To be used when required by the configurator, for example when updating interest rates strategy data * @param asset The address of the underlying asset of the reserve */ function syncIndexesState(address asset) external; /** * @notice Updates interest rates on the reserve data * @dev Only callable by the PoolConfigurator contract * @dev To be used when required by the configurator, for example when updating interest rates strategy data * @param asset The address of the underlying asset of the reserve */ function syncRatesState(address asset) external; /** * @notice Sets the configuration bitmap of the reserve as a whole * @dev Only callable by the PoolConfigurator contract * @param asset The address of the underlying asset of the reserve * @param configuration The new configuration bitmap */ function setConfiguration( address asset, DataTypes.ReserveConfigurationMap calldata configuration ) external; /** * @notice Returns the configuration of the reserve * @param asset The address of the underlying asset of the reserve * @return The configuration of the reserve */ function getConfiguration( address asset ) external view returns (DataTypes.ReserveConfigurationMap memory); /** * @notice Returns the configuration of the user across all the reserves * @param user The user address * @return The configuration of the user */ function getUserConfiguration( address user ) external view returns (DataTypes.UserConfigurationMap memory); /** * @notice Returns the normalized income of the reserve * @param asset The address of the underlying asset of the reserve * @return The reserve's normalized income */ function getReserveNormalizedIncome(address asset) external view returns (uint256); /** * @notice Returns the normalized variable debt per unit of asset * @dev WARNING: This function is intended to be used primarily by the protocol itself to get a * "dynamic" variable index based on time, current stored index and virtual rate at the current * moment (approx. a borrower would get if opening a position). This means that is always used in * combination with variable debt supply/balances. * If using this function externally, consider that is possible to have an increasing normalized * variable debt that is not equivalent to how the variable debt index would be updated in storage * (e.g. only updates with non-zero variable debt supply) * @param asset The address of the underlying asset of the reserve * @return The reserve normalized variable debt */ function getReserveNormalizedVariableDebt(address asset) external view returns (uint256); /** * @notice Returns the state and configuration of the reserve * @param asset The address of the underlying asset of the reserve * @return The state and configuration data of the reserve */ function getReserveData(address asset) external view returns (DataTypes.ReserveDataLegacy memory); /** * @notice Returns the state and configuration of the reserve, including extra data included with Aave v3.1 * @dev DEPRECATED use independent getters instead (getReserveData, getLiquidationGracePeriod) * @param asset The address of the underlying asset of the reserve * @return The state and configuration data of the reserve with virtual accounting */ function getReserveDataExtended( address asset ) external view returns (DataTypes.ReserveData memory); /** * @notice Returns the virtual underlying balance of the reserve * @param asset The address of the underlying asset of the reserve * @return The reserve virtual underlying balance */ function getVirtualUnderlyingBalance(address asset) external view returns (uint128); /** * @notice Validates and finalizes an aToken transfer * @dev Only callable by the overlying aToken of the `asset` * @param asset The address of the underlying asset of the aToken * @param from The user from which the aTokens are transferred * @param to The user receiving the aTokens * @param amount The amount being transferred/withdrawn * @param balanceFromBefore The aToken balance of the `from` user before the transfer * @param balanceToBefore The aToken balance of the `to` user before the transfer */ function finalizeTransfer( address asset, address from, address to, uint256 amount, uint256 balanceFromBefore, uint256 balanceToBefore ) external; /** * @notice Returns the list of the underlying assets of all the initialized reserves * @dev It does not include dropped reserves * @return The addresses of the underlying assets of the initialized reserves */ function getReservesList() external view returns (address[] memory); /** * @notice Returns the number of initialized reserves * @dev It includes dropped reserves * @return The count */ function getReservesCount() external view returns (uint256); /** * @notice Returns the address of the underlying asset of a reserve by the reserve id as stored in the DataTypes.ReserveData struct * @param id The id of the reserve as stored in the DataTypes.ReserveData struct * @return The address of the reserve associated with id */ function getReserveAddressById(uint16 id) external view returns (address); /** * @notice Returns the PoolAddressesProvider connected to this contract * @return The address of the PoolAddressesProvider */ function ADDRESSES_PROVIDER() external view returns (IPoolAddressesProvider); /** * @notice Updates the protocol fee on the bridging * @param bridgeProtocolFee The part of the premium sent to the protocol treasury */ function updateBridgeProtocolFee(uint256 bridgeProtocolFee) external; /** * @notice Updates flash loan premiums. Flash loan premium consists of two parts: * - A part is sent to aToken holders as extra, one time accumulated interest * - A part is collected by the protocol treasury * @dev The total premium is calculated on the total borrowed amount * @dev The premium to protocol is calculated on the total premium, being a percentage of `flashLoanPremiumTotal` * @dev Only callable by the PoolConfigurator contract * @param flashLoanPremiumTotal The total premium, expressed in bps * @param flashLoanPremiumToProtocol The part of the premium sent to the protocol treasury, expressed in bps */ function updateFlashloanPremiums( uint128 flashLoanPremiumTotal, uint128 flashLoanPremiumToProtocol ) external; /** * @notice Configures a new or alters an existing collateral configuration of an eMode. * @dev In eMode, the protocol allows very high borrowing power to borrow assets of the same category. * The category 0 is reserved as it's the default for volatile assets * @param id The id of the category * @param config The configuration of the category */ function configureEModeCategory( uint8 id, DataTypes.EModeCategoryBaseConfiguration memory config ) external; /** * @notice Replaces the current eMode collateralBitmap. * @param id The id of the category * @param collateralBitmap The collateralBitmap of the category */ function configureEModeCategoryCollateralBitmap(uint8 id, uint128 collateralBitmap) external; /** * @notice Replaces the current eMode borrowableBitmap. * @param id The id of the category * @param borrowableBitmap The borrowableBitmap of the category */ function configureEModeCategoryBorrowableBitmap(uint8 id, uint128 borrowableBitmap) external; /** * @notice Returns the data of an eMode category * @dev DEPRECATED use independent getters instead * @param id The id of the category * @return The configuration data of the category */ function getEModeCategoryData( uint8 id ) external view returns (DataTypes.EModeCategoryLegacy memory); /** * @notice Returns the label of an eMode category * @param id The id of the category * @return The label of the category */ function getEModeCategoryLabel(uint8 id) external view returns (string memory); /** * @notice Returns the collateral config of an eMode category * @param id The id of the category * @return The ltv,lt,lb of the category */ function getEModeCategoryCollateralConfig( uint8 id ) external view returns (DataTypes.CollateralConfig memory); /** * @notice Returns the collateralBitmap of an eMode category * @param id The id of the category * @return The collateralBitmap of the category */ function getEModeCategoryCollateralBitmap(uint8 id) external view returns (uint128); /** * @notice Returns the borrowableBitmap of an eMode category * @param id The id of the category * @return The borrowableBitmap of the category */ function getEModeCategoryBorrowableBitmap(uint8 id) external view returns (uint128); /** * @notice Allows a user to use the protocol in eMode * @param categoryId The id of the category */ function setUserEMode(uint8 categoryId) external; /** * @notice Returns the eMode the user is using * @param user The address of the user * @return The eMode id */ function getUserEMode(address user) external view returns (uint256); /** * @notice Resets the isolation mode total debt of the given asset to zero * @dev It requires the given asset has zero debt ceiling * @param asset The address of the underlying asset to reset the isolationModeTotalDebt */ function resetIsolationModeTotalDebt(address asset) external; /** * @notice Sets the liquidation grace period of the given asset * @dev To enable a liquidation grace period, a timestamp in the future should be set, * To disable a liquidation grace period, any timestamp in the past works, like 0 * @param asset The address of the underlying asset to set the liquidationGracePeriod * @param until Timestamp when the liquidation grace period will end **/ function setLiquidationGracePeriod(address asset, uint40 until) external; /** * @notice Returns the liquidation grace period of the given asset * @param asset The address of the underlying asset * @return Timestamp when the liquidation grace period will end **/ function getLiquidationGracePeriod(address asset) external returns (uint40); /** * @notice Returns the total fee on flash loans * @return The total fee on flashloans */ function FLASHLOAN_PREMIUM_TOTAL() external view returns (uint128); /** * @notice Returns the part of the bridge fees sent to protocol * @return The bridge fee sent to the protocol treasury */ function BRIDGE_PROTOCOL_FEE() external view returns (uint256); /** * @notice Returns the part of the flashloan fees sent to protocol * @return The flashloan fee sent to the protocol treasury */ function FLASHLOAN_PREMIUM_TO_PROTOCOL() external view returns (uint128); /** * @notice Returns the maximum number of reserves supported to be listed in this Pool * @return The maximum number of reserves supported */ function MAX_NUMBER_RESERVES() external view returns (uint16); /** * @notice Mints the assets accrued through the reserve factor to the treasury in the form of aTokens * @param assets The list of reserves for which the minting needs to be executed */ function mintToTreasury(address[] calldata assets) external; /** * @notice Rescue and transfer tokens locked in this contract * @param token The address of the token * @param to The address of the recipient * @param amount The amount of token to transfer */ function rescueTokens(address token, address to, uint256 amount) external; /** * @notice Supplies an `amount` of underlying asset into the reserve, receiving in return overlying aTokens. * - E.g. User supplies 100 USDC and gets in return 100 aUSDC * @dev Deprecated: Use the `supply` function instead * @param asset The address of the underlying asset to supply * @param amount The amount to be supplied * @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user * wants to receive them on his own wallet, or a different address if the beneficiary of aTokens * is a different wallet * @param referralCode Code used to register the integrator originating the operation, for potential rewards. * 0 if the action is executed directly by the user, without any middle-man */ function deposit(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external; /** * @notice Gets the address of the external FlashLoanLogic */ function getFlashLoanLogic() external view returns (address); /** * @notice Gets the address of the external BorrowLogic */ function getBorrowLogic() external view returns (address); /** * @notice Gets the address of the external BridgeLogic */ function getBridgeLogic() external view returns (address); /** * @notice Gets the address of the external EModeLogic */ function getEModeLogic() external view returns (address); /** * @notice Gets the address of the external LiquidationLogic */ function getLiquidationLogic() external view returns (address); /** * @notice Gets the address of the external PoolLogic */ function getPoolLogic() external view returns (address); /** * @notice Gets the address of the external SupplyLogic */ function getSupplyLogic() external view returns (address); }
// SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.20; interface IReservoir { struct ExecutionInfo { address module; bytes data; uint256 value; // @dev for a buy is the amount of ETH to send, for a sell is the amount of principal to receive } function execute(ExecutionInfo[] calldata _executionInfos) external payable; }
// SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.20; import "../loans/IMultiSourceLoan.sol"; import "../../interfaces/external/IReservoir.sol"; import "@solmate/tokens/ERC20.sol"; import "@solmate/tokens/ERC721.sol"; import {IPool} from "@aave/interfaces/IPool.sol"; interface IPurchaseBundler { struct ExecutionInfo { IReservoir.ExecutionInfo reservoirExecutionInfo; bool contractMustBeOwner; } struct Taxes { uint128 buyTax; uint128 sellTax; } struct AaveBorrowArgs { IPool pool; address[] assets; uint256[] amounts; } struct ExecuteSellArgs { ERC20[] currencies; uint256[] currencyAmounts; ERC721[] collections; uint256[] tokenIds; address marketPlace; bytes[] executionData; } struct ExecuteSellWithLoanArgs { AaveBorrowArgs borrowArgs; ExecuteSellArgs executeSellArgs; bytes[] loanExecutionData; } /// @notice Buy a number of NFTs using loans to cover part of the price (i.e. BNPL). /// @dev Buy calls emit loan -> Before trying to transfer the NFT but after transfering the principal /// @dev Encoded: emitLoan(IMultiSourceLoan.LoanExecutionData)[] /// @param executionData The data needed to execute the loan + buy the NFT. function buy(bytes[] calldata executionData) external payable returns (uint256[] memory); /// @notice Sell the collateral behind a number of loans (potentially 1) and use proceeds to pay back the loans. /// @dev Encoded: repayLoan(IMultiSourceLoan.LoanRepaymentData)[] /// @param executionData The data needed to execute the loan repayment + sell the NFT. function sell(bytes[] calldata executionData) external; /// @notice Execute a sell signed by the borrower. /// @dev Encoded: repayLoan(IMultiSourceLoan.LoanRepaymentData)[] /// @param currencies The currencies to send. /// @param currencyAmounts The amounts of each currency to send. /// @param collections The collections of the NFTs to receive. /// @param tokenIds The token IDs of the NFTs to receive. /// @param executionData The data needed to execute the loan repayment + sell the NFT. function executeSell( ERC20[] calldata currencies, uint256[] calldata currencyAmounts, ERC721[] calldata collections, uint256[] calldata tokenIds, address marketPlace, bytes[] calldata executionData ) external; /// @notice Execute a sell signed by the borrower using ETH. /// @dev Encoded: repayLoan(IMultiSourceLoan.LoanRepaymentData)[] /// @param wethPrincipalSwapData The data needed to swap the WETH for the loan principal. /// @param executionData The data needed to execute the loan repayment + sell the NFT. function executeSellWithETH( bytes calldata wethPrincipalSwapData, ERC20 principal, ERC721 collection, uint256 tokenId, bytes calldata executionData ) external payable; function executeSellWithLoan(ExecuteSellWithLoanArgs calldata args) external; /// @notice First step to update the MultiSourceLoan address. /// @param newAddress The new address of the MultiSourceLoan. function updateMultiSourceLoanAddressFirst(address newAddress) external; /// @notice Second step to update the MultiSourceLoan address. /// @param newAddress The new address of the MultiSourceLoan. Must match address from first update. function finalUpdateMultiSourceLoanAddress(address newAddress) external; /// @notice Returns the address of the MultiSourceLoan. function getMultiSourceLoanAddress() external view returns (address); /// @return _taxes The current taxes. function getTaxes() external returns (Taxes memory); /// @return _pendingTaxes The pending taxes. function getPendingTaxes() external returns (Taxes memory); /// @return _pendingTaxesSetTime The time when the pending taxes were set. function getPendingTaxesSetTime() external returns (uint256); /// @notice Kicks off the process to update the taxes. /// @param newTaxes New taxes. function updateTaxes(Taxes calldata newTaxes) external; /// @notice Set the taxes if enough notice has been given. function setTaxes() external; }
// SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.20; import "../loans/IMultiSourceLoan.sol"; interface ILoanCallback { error InvalidCallbackError(); /// @notice Called by the MSL contract after the principal of loan has been tranfered (when a loan is initiated) /// but before it tries to transfer the NFT into escrow. /// @param _loan The loan. /// @param _fee The origination fee. /// @param _executionData Execution data for purchase. /// @return The bytes4 magic value. function afterPrincipalTransfer(IMultiSourceLoan.Loan memory _loan, uint256 _fee, bytes calldata _executionData) external returns (bytes4); /// @notice Call by the MSL contract after the NFT has been transfered to the borrower repaying the loan, but before /// transfering the principal to the lender. /// @param _loan The loan. /// @param _executionData Execution data for the offer. /// @return The bytes4 magic value. function afterNFTTransfer(IMultiSourceLoan.Loan memory _loan, bytes calldata _executionData) external returns (bytes4); }
// SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.20; /// @dev CryptoPunksMarket interface. Copied from etherscan: https://etherscan.io/token/0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb#code interface ICryptoPunksMarket { struct Bid { bool hasBid; uint256 punkIndex; address bidder; uint256 value; } /// @notice Transfer a punk that `msg.sender` owns. /// @param to The address to transfer the punk to. /// @param punkIndex The index of the punk to transfer. function transferPunk(address to, uint256 punkIndex) external; /// @notice Withdraw available balance for `msg.sender` function withdraw() external; /// @notice Get owner of punk by punk index. /// @param punkIndex The index of the punk. /// @return The address of the owner. function punkIndexToAddress(uint256 punkIndex) external view returns (address); /// @notice Get the balance of `user`. /// @param user The address of the user. /// @return The balance of the user. function pendingWithdrawals(address user) external view returns (uint256); /// @notice Get the bid for a punk. /// @param punkIndex The index of the punk. /// @return The bid. function punkBids(uint256 punkIndex) external view returns (Bid memory); function enterBidForPunk(uint256 punkIndex) external payable; }
// SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.20; /// @notice Wrapped punk interface. Methods copied from etherscan. interface IWrappedPunk { /// @notice Users need to register a proxy to mint wrappedpunks. function registerProxy() external; /// @notice Get proxy info for a given address. /// @param user The address of the user. /// @return The address of the proxy. function proxyInfo(address user) external view returns (address); /// @notice After sending a punk to the proxy, users need to mint the wrapped punk. /// @param punkIndex The index of the punk to mint. function mint(uint256 punkIndex) external; /// @notice Burn the wrapped punk and get the unwrapped punk. function burn(uint256 punkIndex) external; /// @notice It implements the ERC721 interface. function transferFrom(address from, address to, uint256 tokenId) external; /// @notice It implements the ERC721 interface. function safeTransferFrom(address from, address to, uint256 tokenId) external; /// @notice It implements the ERC721 interface. function ownerOf(uint256 tokenId) external view returns (address); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.21; import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; interface ICryptoPunks721 is IERC721 { function licensingTerms() external pure returns (string memory); function wrapPunk(uint256 punkIndex) external; function wrapPunkBatch(uint256[] calldata punkIndexes) external; function unwrapPunk(uint256 punkIndex) external; function unwrapPunkBatch(uint256[] calldata punkIndexes) external; function migrateLegacyWrappedPunks(uint256[] calldata punkIndexes) external; function rescuePunk(uint256 punkIndex) external; function punkProxyForUser(address user) external view returns (address); function tokensOfOwner(address owner) external view returns (uint256[] memory); function totalSupply() external view returns (uint256); }
// SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.21; // @dev Contract to swap assets in Uniswap V2 and V3 // @dev https://github.com/Uniswap/universal-router interface IUniversalRouter { /// @notice Thrown when a required command has failed error ExecutionFailed(uint256 commandIndex, bytes message); /// @notice Thrown when attempting to send ETH directly to the contract error ETHNotAccepted(); /// @notice Thrown when executing commands with an expired deadline error TransactionDeadlinePassed(); /// @notice Thrown when attempting to execute commands and an incorrect number of inputs are provided error LengthMismatch(); // @notice Thrown when an address that isn't WETH tries to send ETH to the router without calldata error InvalidEthSender(); /// @notice Executes encoded commands along with provided inputs. Reverts if deadline has expired. /// @param commands A set of concatenated commands, each 1 byte in length /// @param inputs An array of byte strings containing abi encoded inputs for each command /// @param deadline The deadline by which the transaction must be executed function execute(bytes calldata commands, bytes[] calldata inputs, uint256 deadline) external payable; } library Commands { // Masks to extract certain bits of commands bytes1 internal constant FLAG_ALLOW_REVERT = 0x80; bytes1 internal constant COMMAND_TYPE_MASK = 0x3f; // Command Types. Maximum supported command at this moment is 0x3f. // The commands are executed in nested if blocks to minimise gas consumption // Command Types where value<=0x07, executed in the first nested-if block uint256 constant V3_SWAP_EXACT_IN = 0x00; uint256 constant V3_SWAP_EXACT_OUT = 0x01; uint256 constant PERMIT2_TRANSFER_FROM = 0x02; uint256 constant PERMIT2_PERMIT_BATCH = 0x03; uint256 constant SWEEP = 0x04; uint256 constant TRANSFER = 0x05; uint256 constant PAY_PORTION = 0x06; // COMMAND_PLACEHOLDER = 0x07; // Command Types where 0x08<=value<=0x0f, executed in the second nested-if block uint256 constant V2_SWAP_EXACT_IN = 0x08; uint256 constant V2_SWAP_EXACT_OUT = 0x09; uint256 constant PERMIT2_PERMIT = 0x0a; uint256 constant WRAP_ETH = 0x0b; uint256 constant UNWRAP_WETH = 0x0c; uint256 constant PERMIT2_TRANSFER_FROM_BATCH = 0x0d; uint256 constant BALANCE_CHECK_ERC20 = 0x0e; // COMMAND_PLACEHOLDER = 0x0f; // Command Types where 0x10<=value<=0x20, executed in the third nested-if block uint256 constant V4_SWAP = 0x10; uint256 constant V3_POSITION_MANAGER_PERMIT = 0x11; uint256 constant V3_POSITION_MANAGER_CALL = 0x12; uint256 constant V4_INITIALIZE_POOL = 0x13; uint256 constant V4_POSITION_MANAGER_CALL = 0x14; // COMMAND_PLACEHOLDER = 0x15 -> 0x20 // Command Types where 0x21<=value<=0x3f uint256 constant EXECUTE_SUB_PLAN = 0x21; // COMMAND_PLACEHOLDER for 0x22 to 0x3f } library CommandInput { struct V3SwapExactIn { address recipient; uint256 amountIn; uint256 amountOutMin; bytes path; bool payerIsUser; } struct V3SwapExactOut { address recipient; uint256 amountOut; uint256 amountInMax; bytes path; bool payerIsUser; } }
// SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.21; import "./TwoStepOwned.sol"; import "../InputChecker.sol"; abstract contract WithProtocolFee is TwoStepOwned { using InputChecker for address; /// @notice Recipient address and fraction of gains charged by the protocol. struct ProtocolFee { address recipient; uint256 fraction; } uint256 public constant FEE_UPDATE_NOTICE = 30 days; /// @notice Protocol fee charged on gains. ProtocolFee internal _protocolFee; /// @notice Set as the target new protocol fee. ProtocolFee internal _pendingProtocolFee; /// @notice Set when the protocol fee updating mechanisms starts. uint256 internal _pendingProtocolFeeSetTime; event ProtocolFeeUpdated(ProtocolFee fee); event ProtocolFeePendingUpdate(ProtocolFee fee); error TooEarlyError(uint256 _pendingProtocolFeeSetTime); /// @notice Constructor /// @param _owner The owner of the contract /// @param _minWaitTime The time to wait before a new owner can be set /// @param __protocolFee The protocol fee constructor(address _owner, uint256 _minWaitTime, ProtocolFee memory __protocolFee) TwoStepOwned(_owner, _minWaitTime) { _protocolFee = __protocolFee; _pendingProtocolFeeSetTime = type(uint256).max; } /// @return protocolFee The Protocol fee. function getProtocolFee() external view returns (ProtocolFee memory) { return _protocolFee; } /// @return pendingProtocolFee The pending protocol fee. function getPendingProtocolFee() external view returns (ProtocolFee memory) { return _pendingProtocolFee; } /// @return protocolFeeSetTime Time when the protocol fee was set to be changed. function getPendingProtocolFeeSetTime() external view returns (uint256) { return _pendingProtocolFeeSetTime; } /// @notice Kicks off the process to update the protocol fee. /// @param _newProtocolFee New protocol fee. function updateProtocolFee(ProtocolFee calldata _newProtocolFee) external onlyOwner { _newProtocolFee.recipient.checkNotZero(); _pendingProtocolFee = _newProtocolFee; _pendingProtocolFeeSetTime = block.timestamp; emit ProtocolFeePendingUpdate(_pendingProtocolFee); } /// @notice Set the protocol fee if enough notice has been given. function setProtocolFee() external virtual { if (block.timestamp < _pendingProtocolFeeSetTime + FEE_UPDATE_NOTICE) { revert TooSoonError(); } ProtocolFee memory protocolFee = _pendingProtocolFee; _protocolFee = protocolFee; emit ProtocolFeeUpdated(protocolFee); } }
// SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.21; import "@delegate/IDelegateRegistry.sol"; import "@openzeppelin/utils/cryptography/ECDSA.sol"; import "@solmate/tokens/ERC20.sol"; import "@solmate/tokens/ERC721.sol"; import "@solmate/utils/FixedPointMathLib.sol"; import "@solmate/utils/ReentrancyGuard.sol"; import "@solmate/utils/SafeTransferLib.sol"; import "../../interfaces/validators/IOfferValidator.sol"; import "../../interfaces/INFTFlashAction.sol"; import "../../interfaces/loans/ILoanManager.sol"; import "../../interfaces/loans/ILoanManagerRegistry.sol"; import "../../interfaces/loans/IMultiSourceLoan.sol"; import "../utils/Hash.sol"; import "../utils/Interest.sol"; import "../Multicall.sol"; import "./BaseLoan.sol"; /// @title MultiSourceLoan (v3.1) /// @author Florida St /// @notice Loan contract that allows for multiple tranches with different /// seniorities. Each loan is collateralized by an NFT. Loans have a duration, /// principal, and APR. Loans can be refinanced automatically by lenders (if terms /// are improved). Borrowers can also get renegotiation offers which they can then /// accept. If a loan is not repaid by its end time, it's considered to have defaulted. /// If it had only one lender behind it, then the lender (unless it's a pool), can claim /// the collateral. If there are multiple lenders or the sole lender is a pool, then there's /// a liquidation process (run by an instance of `ILoanLiquidator`). contract MultiSourceLoan is IMultiSourceLoan, Multicall, ReentrancyGuard, BaseLoan { using FixedPointMathLib for uint256; using Hash for ExecutionData; using Hash for Loan; using Hash for LoanOffer; using Hash for SignableRepaymentData; using Hash for RenegotiationOffer; using InputChecker for address; using Interest for uint256; using ECDSA for bytes32; using MessageHashUtils for bytes32; using SafeTransferLib for ERC20; /// @notice Loan Id to hash mapping(uint256 loanId => bytes32 loanHash) private _loans; /// This is used in _getMinTranchePrincipal. uint256 private constant _MAX_RATIO_TRANCHE_MIN_PRINCIPAL = 2; /// @notice Maximum number of tranches per loan uint256 public immutable getMaxTranches; /// @notice delegate registry address public immutable getDelegateRegistry; /// @notice Contract to execute flash actions. address public getFlashActionContract; /// @notice Loan manager registry (we currently have Gondi's pools) ILoanManagerRegistry public immutable getLoanManagerRegistry; /// @notice Min lock period for a tranche uint256 private _minLockPeriod; error InvalidParametersError(); error MismatchError(); error InvalidCollateralIdError(); error InvalidMethodError(); error InvalidAddressesError(); error InvalidCallerError(); error InvalidTrancheError(); error InvalidRenegotiationOfferError(); error TooManyTranchesError(); error LoanExpiredError(); error NFTNotReturnedError(); error TrancheCannotBeRefinancedError(uint256 minTimestamp); error LoanLockedError(); /// @param loanLiquidator Address of the liquidator contract. /// @param protocolFee Protocol fee charged on gains. /// @param currencyManager Address of the currency manager. /// @param collectionManager Address of the collection manager. /// @param maxTranches Maximum number of tranches per loan. /// @param minLockPeriod Minimum lock period for a tranche/loan. /// @param delegateRegistry Address of the delegate registry (Delegate.xyz). /// @param loanManagerRegistry Address of the loan manager registry. /// @param flashActionContract Address of the flash action contract. /// @param minWaitTime The time to wait before a new owner can be set. constructor( address loanLiquidator, ProtocolFee memory protocolFee, address currencyManager, address collectionManager, uint256 maxTranches, uint256 minLockPeriod, address delegateRegistry, address loanManagerRegistry, address flashActionContract, uint256 minWaitTime ) BaseLoan( "GONDI_MULTI_SOURCE_LOAN", currencyManager, collectionManager, protocolFee, loanLiquidator, tx.origin, minWaitTime ) { loanLiquidator.checkNotZero(); _minLockPeriod = minLockPeriod; getMaxTranches = maxTranches; getDelegateRegistry = delegateRegistry; getFlashActionContract = flashActionContract; getLoanManagerRegistry = ILoanManagerRegistry(loanManagerRegistry); } /// @inheritdoc IMultiSourceLoan function emitLoan(LoanExecutionData calldata _loanExecutionData) external nonReentrant returns (uint256, Loan memory) { address borrower = _loanExecutionData.borrower; ExecutionData calldata executionData = _loanExecutionData.executionData; (address principalAddress, address nftCollateralAddress) = _getAddressesFromExecutionData(executionData); OfferExecution[] calldata offerExecution = executionData.offerExecution; _validateExecutionData(_loanExecutionData, borrower); _checkWhitelists(principalAddress, nftCollateralAddress); (uint256 loanId, uint256[] memory offerIds, Loan memory loan, uint256 totalFee) = _processOffersFromExecutionData( borrower, executionData.principalReceiver, principalAddress, nftCollateralAddress, executionData.tokenId, executionData.duration, offerExecution ); if (_hasCallback(executionData.callbackData)) { handleAfterPrincipalTransferCallback(loan, msg.sender, executionData.callbackData, totalFee); } ERC721(nftCollateralAddress).transferFrom(borrower, address(this), executionData.tokenId); _loans[loanId] = loan.hash(); emit LoanEmitted(loanId, offerIds, loan, totalFee); return (loanId, loan); } /// @inheritdoc IMultiSourceLoan function refinanceFull( RenegotiationOffer calldata _renegotiationOffer, Loan memory _loan, bytes calldata _renegotiationOfferSignature ) external nonReentrant returns (uint256, Loan memory) { _baseLoanChecks(_renegotiationOffer.loanId, _loan); _baseRenegotiationChecks(_renegotiationOffer, _loan); if (_renegotiationOffer.trancheIndex.length != _loan.tranche.length) { revert InvalidRenegotiationOfferError(); } bool lenderInitiated = msg.sender == _renegotiationOffer.lender; uint256 netNewLender = _renegotiationOffer.principalAmount - _renegotiationOffer.fee; uint256 totalAccruedInterest; uint256 totalAnnualInterest; /// @dev If it's lender initiated, needs to be strictly better. if (lenderInitiated) { (totalAccruedInterest, totalAnnualInterest,) = _processOldTranchesFull(_renegotiationOffer, _loan, lenderInitiated, 0); if (_isLoanLocked(_loan.startTime, _loan.duration)) { revert LoanLockedError(); } _checkStrictlyBetter( _renegotiationOffer.principalAmount, _loan.principalAmount, _renegotiationOffer.duration + block.timestamp, _loan.duration + _loan.startTime, _renegotiationOffer.aprBps, totalAnnualInterest / _loan.principalAmount, _renegotiationOffer.fee ); if (_renegotiationOffer.principalAmount > _loan.principalAmount) { ERC20(_loan.principalAddress).safeTransferFrom( _renegotiationOffer.lender, _loan.borrower, _renegotiationOffer.principalAmount - _loan.principalAmount ); } } else if (msg.sender != _loan.borrower) { revert InvalidCallerError(); } else { (totalAccruedInterest, totalAnnualInterest, netNewLender) = _processOldTranchesFull(_renegotiationOffer, _loan, lenderInitiated, netNewLender); /// @notice Borrowers clears interest _checkSignature(_renegotiationOffer.lender, _renegotiationOffer.hash(), _renegotiationOfferSignature); if (netNewLender > 0) { ERC20(_loan.principalAddress).safeTransferFrom(_renegotiationOffer.lender, _loan.borrower, netNewLender); } totalAccruedInterest = 0; } uint256 newLoanId = _getAndSetNewLoanId(); Tranche[] memory newTranche = new Tranche[](1); newTranche[0] = Tranche( newLoanId, 0, _renegotiationOffer.principalAmount, _renegotiationOffer.lender, totalAccruedInterest, block.timestamp, _renegotiationOffer.aprBps ); _loan.tranche = newTranche; _loan.startTime = block.timestamp; _loan.duration = _renegotiationOffer.duration; _loan.principalAmount = _renegotiationOffer.principalAmount; _loans[newLoanId] = _loan.hash(); delete _loans[_renegotiationOffer.loanId]; emit LoanRefinanced( _renegotiationOffer.renegotiationId, _renegotiationOffer.loanId, newLoanId, _loan, _renegotiationOffer.fee ); return (newLoanId, _loan); } /// @inheritdoc IMultiSourceLoan function refinancePartial(RenegotiationOffer calldata _renegotiationOffer, Loan memory _loan) external nonReentrant returns (uint256, Loan memory) { if (msg.sender != _renegotiationOffer.lender) { revert InvalidCallerError(); } if (_isLoanLocked(_loan.startTime, _loan.duration)) { revert LoanLockedError(); } if (_renegotiationOffer.trancheIndex.length == 0) { revert InvalidRenegotiationOfferError(); } uint256 loanId = _renegotiationOffer.loanId; _baseLoanChecks(loanId, _loan); _baseRenegotiationChecks(_renegotiationOffer, _loan); uint256 newLoanId = _getAndSetNewLoanId(); uint256 totalProtocolFee; uint256 totalAnnualInterest; uint256 totalRefinanced; /// @dev bring to mem uint256 minImprovementApr = _minImprovementApr; /// @dev We iterate over all tranches to execute repayments. uint256 totalTranchesRenegotiated = _renegotiationOffer.trancheIndex.length; for (uint256 i; i < totalTranchesRenegotiated;) { uint256 index = _renegotiationOffer.trancheIndex[i]; if (index >= _loan.tranche.length) { revert InvalidRenegotiationOfferError(); } Tranche memory tranche = _loan.tranche[index]; _checkTrancheStrictly(true, tranche.aprBps, _renegotiationOffer.aprBps, minImprovementApr); (uint256 accruedInterest, uint256 thisProtocolFee,) = _processOldTranche( _renegotiationOffer.lender, _loan.borrower, _loan.principalAddress, tranche, _loan.startTime + _loan.duration, _loan.protocolFee, type(uint256).max ); unchecked { totalRefinanced += tranche.principalAmount; totalAnnualInterest += tranche.principalAmount * tranche.aprBps; totalProtocolFee += thisProtocolFee; } tranche.loanId = newLoanId; tranche.lender = _renegotiationOffer.lender; tranche.accruedInterest = accruedInterest; tranche.startTime = block.timestamp; tranche.aprBps = _renegotiationOffer.aprBps; unchecked { ++i; } } if (_renegotiationOffer.principalAmount != totalRefinanced) { revert InvalidRenegotiationOfferError(); } _handleProtocolFeeForFee( _loan.principalAddress, _renegotiationOffer.lender, totalProtocolFee, _protocolFee.recipient ); _loans[newLoanId] = _loan.hash(); delete _loans[loanId]; /// @dev Here reneg fee is always 0 emit LoanRefinanced(_renegotiationOffer.renegotiationId, loanId, newLoanId, _loan, 0); return (newLoanId, _loan); } /// @inheritdoc IMultiSourceLoan function refinanceFromLoanExecutionData( uint256 _loanId, Loan calldata _loan, LoanExecutionData calldata _loanExecutionData ) external nonReentrant returns (uint256, Loan memory) { if (msg.sender != _loan.borrower) { revert InvalidCallerError(); } _baseLoanChecks(_loanId, _loan); ExecutionData calldata executionData = _loanExecutionData.executionData; /// @dev We ignore the borrower in executionData, and used existing one. address borrower = _loan.borrower; (address principalAddress, address nftCollateralAddress) = _getAddressesFromExecutionData(executionData); OfferExecution[] calldata offerExecution = executionData.offerExecution; _validateExecutionData(_loanExecutionData, borrower); _checkWhitelists(principalAddress, nftCollateralAddress); if (_loan.principalAddress != principalAddress || _loan.nftCollateralAddress != nftCollateralAddress) { revert InvalidAddressesError(); } if (_loan.nftCollateralTokenId != executionData.tokenId) { revert InvalidCollateralIdError(); } /// @dev We first process the incoming offers so borrower gets the capital. After that, we process repayments. /// NFT doesn't need to be transfered (it was already in escrow) (uint256 newLoanId, uint256[] memory offerIds, Loan memory loan, uint256 totalFee) = _processOffersFromExecutionData( borrower, executionData.principalReceiver, principalAddress, nftCollateralAddress, executionData.tokenId, executionData.duration, offerExecution ); _processRepayments(_loan); emit LoanRefinancedFromNewOffers(_loanId, newLoanId, loan, offerIds, totalFee); _loans[newLoanId] = loan.hash(); delete _loans[_loanId]; return (newLoanId, loan); } /// @inheritdoc IMultiSourceLoan function addNewTranche( RenegotiationOffer calldata _renegotiationOffer, Loan memory _loan, bytes calldata _renegotiationOfferSignature ) external nonReentrant returns (uint256, Loan memory) { if (msg.sender != _loan.borrower) { revert InvalidCallerError(); } uint256 loanId = _renegotiationOffer.loanId; _baseLoanChecks(loanId, _loan); _baseRenegotiationChecks(_renegotiationOffer, _loan); _checkSignature(_renegotiationOffer.lender, _renegotiationOffer.hash(), _renegotiationOfferSignature); if (_renegotiationOffer.trancheIndex.length != 1 || _renegotiationOffer.trancheIndex[0] != _loan.tranche.length) { revert InvalidRenegotiationOfferError(); } if (_loan.tranche.length == getMaxTranches) { revert TooManyTranchesError(); } uint256 newLoanId = _getAndSetNewLoanId(); Loan memory loanWithTranche = _addNewTranche(newLoanId, _loan, _renegotiationOffer); _loans[newLoanId] = loanWithTranche.hash(); delete _loans[loanId]; ERC20(_loan.principalAddress).safeTransferFrom( _renegotiationOffer.lender, _loan.borrower, _renegotiationOffer.principalAmount - _renegotiationOffer.fee ); if (_renegotiationOffer.fee != 0) { /// @dev Cached ERC20(_loan.principalAddress).safeTransferFrom( _renegotiationOffer.lender, _protocolFee.recipient, _renegotiationOffer.fee.mulDivUp(_loan.protocolFee, _PRECISION) ); } emit LoanRefinanced( _renegotiationOffer.renegotiationId, loanId, newLoanId, loanWithTranche, _renegotiationOffer.fee ); return (newLoanId, loanWithTranche); } /// @inheritdoc IMultiSourceLoan function repayLoan(LoanRepaymentData calldata _repaymentData) external override nonReentrant { uint256 loanId = _repaymentData.data.loanId; Loan calldata loan = _repaymentData.loan; /// @dev If the caller is not the borrower itself, check the signature to avoid someone else forcing an unwanted repayment. if (msg.sender != loan.borrower) { _checkSignature(loan.borrower, _repaymentData.data.hash(), _repaymentData.borrowerSignature); } _baseLoanChecks(loanId, loan); /// @dev Unlikely this is used outside of the callback with a seaport sell, but leaving here in case that's not correct. if (_repaymentData.data.shouldDelegate) { IDelegateRegistry(getDelegateRegistry).delegateERC721( loan.borrower, loan.nftCollateralAddress, loan.nftCollateralTokenId, bytes32(""), true ); } ERC721(loan.nftCollateralAddress).transferFrom(address(this), loan.borrower, loan.nftCollateralTokenId); /// @dev After returning the NFT to the borrower, check if there's an action to be taken (eg: sell it to cover repayment). if (_hasCallback(_repaymentData.data.callbackData)) { handleAfterNFTTransferCallback(loan, msg.sender, _repaymentData.data.callbackData); } (uint256 totalRepayment, uint256 totalProtocolFee) = _processRepayments(loan); emit LoanRepaid(loanId, totalRepayment, totalProtocolFee); /// @dev Reclaim space. delete _loans[loanId]; } /// @inheritdoc IMultiSourceLoan function liquidateLoan(uint256 _loanId, Loan calldata _loan) external override nonReentrant returns (bytes memory) { if (_loan.hash() != _loans[_loanId]) { revert InvalidLoanError(_loanId); } (bool liquidated, bytes memory liquidation) = _liquidateLoan( _loanId, _loan, _loan.tranche.length == 1 && !getLoanManagerRegistry.isLoanManager(_loan.tranche[0].lender) ); if (liquidated) { delete _loans[_loanId]; } return liquidation; } /// @inheritdoc IMultiSourceLoan function loanLiquidated(uint256 _loanId, Loan calldata _loan) external override onlyLiquidator { if (_loan.hash() != _loans[_loanId]) { revert InvalidLoanError(_loanId); } emit LoanLiquidated(_loanId); /// @dev Reclaim space. delete _loans[_loanId]; } /// @inheritdoc IMultiSourceLoan function delegate(uint256 _loanId, Loan calldata loan, address _delegate, bytes32 _rights, bool _value) external { if (loan.hash() != _loans[_loanId]) { revert InvalidLoanError(_loanId); } if (msg.sender != loan.borrower) { revert InvalidCallerError(); } IDelegateRegistry(getDelegateRegistry).delegateERC721( _delegate, loan.nftCollateralAddress, loan.nftCollateralTokenId, _rights, _value ); emit Delegated(_loanId, _delegate, _rights, _value); } /// @inheritdoc IMultiSourceLoan function revokeDelegate(address _delegate, address _collection, uint256 _tokenId, bytes32 _rights) external { if (ERC721(_collection).ownerOf(_tokenId) == address(this)) { revert InvalidMethodError(); } IDelegateRegistry(getDelegateRegistry).delegateERC721(_delegate, _collection, _tokenId, _rights, false); emit RevokeDelegate(_delegate, _collection, _tokenId, _rights); } /// @inheritdoc IMultiSourceLoan function getMinLockPeriod() external view returns (uint256) { return _minLockPeriod; } /// @inheritdoc IMultiSourceLoan function setMinLockPeriod(uint256 __minLockPeriod) external onlyOwner { _minLockPeriod = __minLockPeriod; emit MinLockPeriodUpdated(__minLockPeriod); } /// @inheritdoc IMultiSourceLoan function getLoanHash(uint256 _loanId) external view returns (bytes32) { return _loans[_loanId]; } /// @inheritdoc IMultiSourceLoan function executeFlashAction(uint256 _loanId, Loan calldata _loan, address _target, bytes calldata _data) external nonReentrant { if (_loan.hash() != _loans[_loanId]) { revert InvalidLoanError(_loanId); } if (msg.sender != _loan.borrower) { revert InvalidCallerError(); } address flashActionContract = getFlashActionContract; ERC721(_loan.nftCollateralAddress).transferFrom(address(this), flashActionContract, _loan.nftCollateralTokenId); INFTFlashAction(flashActionContract).execute( _loan.nftCollateralAddress, _loan.nftCollateralTokenId, _target, _data ); if (ERC721(_loan.nftCollateralAddress).ownerOf(_loan.nftCollateralTokenId) != address(this)) { revert NFTNotReturnedError(); } emit FlashActionExecuted(_loanId, _target, _data); } /// @inheritdoc IMultiSourceLoan function setFlashActionContract(address _newFlashActionContract) external onlyOwner { getFlashActionContract = _newFlashActionContract; emit FlashActionContractUpdated(_newFlashActionContract); } /// @notice Process repayments for tranches upon a full renegotiation. /// @param _renegotiationOffer The renegotiation offer. /// @param _loan The loan to be processed. /// @param _isStrictlyBetter Whether the new tranche needs to be strictly better than all previous ones. /// @param _remainingNewLender Amount left for new lender to pay function _processOldTranchesFull( RenegotiationOffer calldata _renegotiationOffer, Loan memory _loan, bool _isStrictlyBetter, uint256 _remainingNewLender ) private returns (uint256 totalAccruedInterest, uint256 totalAnnualInterest, uint256 remainingNewLender) { uint256 totalProtocolFee = _renegotiationOffer.fee.mulDivUp(_loan.protocolFee, _PRECISION); unchecked { _remainingNewLender += totalProtocolFee; /// @dev bring to mem uint256 minImprovementApr = _minImprovementApr; remainingNewLender = _isStrictlyBetter ? type(uint256).max : _remainingNewLender; // We iterate first for the new lender and then for the rest. // This way if he is owed some principal, // it's discounted before transfering to the other lenders for (uint256 i = 0; i < _loan.tranche.length << 1;) { Tranche memory tranche = _loan.tranche[i % _loan.tranche.length]; bool onlyNewLenderPass = i < _loan.tranche.length; bool isNewLender = tranche.lender == _renegotiationOffer.lender; ++i; if (onlyNewLenderPass != isNewLender) continue; uint256 accruedInterest; uint256 thisProtocolFee; (accruedInterest, thisProtocolFee, remainingNewLender) = _processOldTranche( _renegotiationOffer.lender, _loan.borrower, _loan.principalAddress, tranche, _loan.startTime + _loan.duration, _loan.protocolFee, remainingNewLender ); _checkTrancheStrictly(_isStrictlyBetter, tranche.aprBps, _renegotiationOffer.aprBps, minImprovementApr); totalAnnualInterest += tranche.principalAmount * tranche.aprBps; totalAccruedInterest += accruedInterest; totalProtocolFee += thisProtocolFee; } uint256 lenderFee = remainingNewLender > totalProtocolFee ? totalProtocolFee : remainingNewLender; uint256 borrowerFee = totalProtocolFee - lenderFee; _handleProtocolFeeForFee( _loan.principalAddress, _renegotiationOffer.lender, lenderFee, _protocolFee.recipient ); _handleProtocolFeeForFee(_loan.principalAddress, _loan.borrower, borrowerFee, _protocolFee.recipient); remainingNewLender -= lenderFee; } } /// @notice Process the current source tranche during a renegotiation. /// @param _lender The new lender. /// @param _borrower The borrower of the loan. /// @param _principalAddress The principal address of the loan. /// @param _tranche The tranche to be processed. /// @param _endTime The end time of the loan. /// @param _protocolFeeFraction The protocol fee fraction. /// @param _remainingNewLender The amount left for the new lender to pay. /// @return accruedInterest The accrued interest paid. /// @return thisProtocolFee The protocol fee paid for this tranche. /// @return remainingNewLender The amount left for the new lender to pay. function _processOldTranche( address _lender, address _borrower, address _principalAddress, Tranche memory _tranche, uint256 _endTime, uint256 _protocolFeeFraction, uint256 _remainingNewLender ) private returns (uint256 accruedInterest, uint256 thisProtocolFee, uint256 remainingNewLender) { uint256 unlockTime = _getUnlockedTime(_tranche.startTime, _endTime); if (unlockTime > block.timestamp) { revert TrancheCannotBeRefinancedError(unlockTime); } unchecked { accruedInterest = _tranche.principalAmount.getInterest(_tranche.aprBps, block.timestamp - _tranche.startTime); thisProtocolFee = accruedInterest.mulDivUp(_protocolFeeFraction, _PRECISION); accruedInterest += _tranche.accruedInterest; } if (getLoanManagerRegistry.isLoanManager(_tranche.lender)) { ILoanManager(_tranche.lender).loanRepayment( _tranche.loanId, _tranche.principalAmount, _tranche.aprBps, _tranche.accruedInterest, _protocolFeeFraction, _tranche.startTime ); } uint256 oldLenderDebt; unchecked { oldLenderDebt = _tranche.principalAmount + accruedInterest - thisProtocolFee; } ERC20 asset = ERC20(_principalAddress); if (oldLenderDebt > _remainingNewLender) { /// @dev already checked in the condition asset.safeTransferFrom(_borrower, _tranche.lender, oldLenderDebt - _remainingNewLender); oldLenderDebt = _remainingNewLender; } if (oldLenderDebt > 0) { if (_lender != _tranche.lender) { asset.safeTransferFrom(_lender, _tranche.lender, oldLenderDebt); } /// @dev oldLenderDebt < _remainingNewLender because it would enter previous condition if not and set to _remainingNewLender unchecked { _remainingNewLender -= oldLenderDebt; } } remainingNewLender = _remainingNewLender; } /// @notice Basic loan checks (check if the hash is correct) + whether loan is still active. /// @param _loanId The loan ID. /// @param _loan The loan to be checked. function _baseLoanChecks(uint256 _loanId, Loan memory _loan) private view { if (_loan.hash() != _loans[_loanId]) { revert InvalidLoanError(_loanId); } if (_loan.startTime + _loan.duration <= block.timestamp) { revert LoanExpiredError(); } } /// @notice Basic renegotiation checks. Check basic parameters + expiration + whether the offer is active. function _baseRenegotiationChecks(RenegotiationOffer calldata _renegotiationOffer, Loan memory _loan) private view { if ( (_renegotiationOffer.principalAmount == 0) || (_loan.tranche.length < _renegotiationOffer.trancheIndex.length) ) { revert InvalidRenegotiationOfferError(); } if (block.timestamp > _renegotiationOffer.expirationTime) { revert ExpiredOfferError(_renegotiationOffer.expirationTime); } uint256 renegotiationId = _renegotiationOffer.renegotiationId; address lender = _renegotiationOffer.lender; if (isRenegotiationOfferCancelled[lender][renegotiationId]) { revert CancelledOrExecutedOfferError(lender, renegotiationId); } } /// @notice Protocol fee for fees charged on offers/renegotationOffers. /// @param _principalAddress The principal address of the loan. /// @param _lender The lender of the loan. /// @param _fee The fee to be charged. /// @param _feeRecipient The protocol fee recipient. function _handleProtocolFeeForFee(address _principalAddress, address _lender, uint256 _fee, address _feeRecipient) private { if (_fee != 0) { ERC20(_principalAddress).safeTransferFrom(_lender, _feeRecipient, _fee); } } /// @notice Check condition for strictly better tranches /// @param _isStrictlyBetter Whether the new tranche needs to be strictly better than the old one. /// @param _currentAprBps The current apr of the tranche. /// @param _targetAprBps The target apr of the tranche. /// @param __minImprovementApr The minimum improvement in APR. function _checkTrancheStrictly( bool _isStrictlyBetter, uint256 _currentAprBps, uint256 _targetAprBps, uint256 __minImprovementApr ) private pure { /// @dev If _isStrictlyBetter is set, and the new apr is higher, then it'll underflow. if ( _isStrictlyBetter && ((_currentAprBps - _targetAprBps).mulDivDown(_PRECISION, _currentAprBps) < __minImprovementApr) ) { revert InvalidRenegotiationOfferError(); } } /// @dev Tranches are locked from any refi after they are initiated for some time. function _getUnlockedTime(uint256 _trancheStartTime, uint256 _loanEndTime) private view returns (uint256) { uint256 delta; unchecked { delta = _loanEndTime - _trancheStartTime; } return _trancheStartTime + delta.mulDivUp(_minLockPeriod, _PRECISION); } /// @dev Loans are locked from lender initiated refis in the end. function _isLoanLocked(uint256 _loanStartTime, uint256 _loanDuration) private view returns (bool) { unchecked { /// @dev doesn't overflow because _minLockPeriod should be < 1 return block.timestamp > _loanStartTime + _loanDuration - _loanDuration.mulDivUp(_minLockPeriod, _PRECISION); } } /// @notice Base ExecutionData Checks /// @dev Note that we do not validate fee < principalAmount since this is done in the child class in this case. /// @param _offerExecution The offer execution. /// @param _tokenId The token ID. /// @param _lender The lender. /// @param _duration The duration. /// @param _lenderOfferSignature The signature of the lender of LoanOffer. /// @param _feeFraction The protocol fee fraction. /// @param _totalAmount The total amount ahead. function _validateOfferExecution( OfferExecution calldata _offerExecution, uint256 _tokenId, address _lender, uint256 _duration, bytes calldata _lenderOfferSignature, uint256 _feeFraction, uint256 _totalAmount ) private { LoanOffer calldata offer = _offerExecution.offer; address lender = offer.lender; uint256 offerId = offer.offerId; uint256 totalAmountAfterExecution = _offerExecution.amount + _totalAmount; if (lender.code.length != 0 && getLoanManagerRegistry.isLoanManager(lender)) { ILoanManager(lender).validateOffer(_tokenId, abi.encode(_offerExecution), _feeFraction); } else { _checkSignature(lender, offer.hash(), _lenderOfferSignature); } if (block.timestamp > offer.expirationTime) { revert ExpiredOfferError(offer.expirationTime); } if (isOfferCancelled[_lender][offerId] || (offerId <= minOfferId[_lender])) { revert CancelledOrExecutedOfferError(_lender, offerId); } if (totalAmountAfterExecution > offer.principalAmount) { revert InvalidAmountError(totalAmountAfterExecution, offer.principalAmount); } if (offer.duration == 0 || _duration > offer.duration) { revert InvalidDurationError(); } if (offer.aprBps == 0) { revert ZeroInterestError(); } if ((offer.capacity != 0) && (_used[_lender][offer.offerId] + _offerExecution.amount > offer.capacity)) { revert MaxCapacityExceededError(); } _checkValidators(_offerExecution.offer, _tokenId); } /// @notice Basic checks (expiration / signature if diff than borrower) for execution data. function _validateExecutionData(LoanExecutionData calldata _executionData, address _borrower) private view { if (msg.sender != _borrower) { _checkSignature(_borrower, _executionData.executionData.hash(), _executionData.borrowerOfferSignature); } if (block.timestamp > _executionData.executionData.expirationTime) { revert ExpiredOfferError(_executionData.executionData.expirationTime); } if (_executionData.executionData.offerExecution.length > getMaxTranches) { revert TooManyTranchesError(); } } /// @notice Extract addresses from first offer. Used for validations. /// @param _executionData Execution data. /// @return principalAddress Address of the principal token. /// @return nftCollateralAddress Address of the NFT collateral. function _getAddressesFromExecutionData(ExecutionData calldata _executionData) private pure returns (address, address) { LoanOffer calldata one = _executionData.offerExecution[0].offer; return (one.principalAddress, one.nftCollateralAddress); } /// @notice Check addresses are whitelisted. /// @param _principalAddress Address of the principal token. /// @param _nftCollateralAddress Address of the NFT collateral. function _checkWhitelists(address _principalAddress, address _nftCollateralAddress) private view { if (!_currencyManager.isWhitelisted(_principalAddress)) { revert CurrencyNotWhitelistedError(); } if (!_collectionManager.isWhitelisted(_nftCollateralAddress)) { revert CollectionNotWhitelistedError(); } } /// @notice Check principal/collateral addresses match. /// @param _offer The offer to check. /// @param _principalAddress Address of the principal token. /// @param _nftCollateralAddress Address of the NFT collateral. /// @param _amountWithInterestAhead Amount of more senior principal + max accrued interest ahead. function _checkOffer( LoanOffer calldata _offer, address _principalAddress, address _nftCollateralAddress, uint256 _amountWithInterestAhead ) private pure { if (_offer.principalAddress != _principalAddress || _offer.nftCollateralAddress != _nftCollateralAddress) { revert InvalidAddressesError(); } if (_amountWithInterestAhead > _offer.maxSeniorRepayment) { revert InvalidTrancheError(); } } /// @notice Check generic offer validators for a given offer or /// an exact match if no validators are given. The validators /// check is performed only if tokenId is set to 0. /// Having one empty validator is used for collection offers (all IDs match). /// @param _loanOffer The loan offer to check. /// @param _tokenId The token ID to check. function _checkValidators(LoanOffer calldata _loanOffer, uint256 _tokenId) private view { uint256 offerTokenId = _loanOffer.nftCollateralTokenId; if (_loanOffer.nftCollateralTokenId != 0) { if (offerTokenId != _tokenId) { revert InvalidCollateralIdError(); } } else { uint256 totalValidators = _loanOffer.validators.length; if (totalValidators == 0 && _tokenId != 0) { revert InvalidCollateralIdError(); } else if ((totalValidators == 1) && _loanOffer.validators[0].validator == address(0)) { return; } for (uint256 i = 0; i < totalValidators;) { IBaseLoan.OfferValidator memory thisValidator = _loanOffer.validators[i]; IOfferValidator(thisValidator.validator).validateOffer(_loanOffer, _tokenId, thisValidator.arguments); unchecked { ++i; } } } } /// @dev Check new trnches are at least this big. function _getMinTranchePrincipal(uint256 _loanPrincipal) private view returns (uint256) { return _loanPrincipal / (_MAX_RATIO_TRANCHE_MIN_PRINCIPAL * getMaxTranches); } function _hasCallback(bytes calldata _callbackData) private pure returns (bool) { return _callbackData.length != 0; } function _processRepayments(Loan calldata loan) private returns (uint256, uint256) { bool withProtocolFee = loan.protocolFee != 0; uint256 totalRepayment = 0; uint256 totalProtocolFee = 0; ERC20 asset = ERC20(loan.principalAddress); uint256 totalTranches = loan.tranche.length; for (uint256 i; i < totalTranches;) { Tranche memory tranche = loan.tranche[i]; uint256 newInterest = tranche.principalAmount.getInterest(tranche.aprBps, block.timestamp - tranche.startTime); uint256 thisProtocolFee = 0; if (withProtocolFee) { thisProtocolFee = newInterest.mulDivUp(loan.protocolFee, _PRECISION); unchecked { totalProtocolFee += thisProtocolFee; } } uint256 repayment = tranche.principalAmount + tranche.accruedInterest + newInterest - thisProtocolFee; asset.safeTransferFrom(loan.borrower, tranche.lender, repayment); unchecked { totalRepayment += repayment; } if (getLoanManagerRegistry.isLoanManager(tranche.lender)) { ILoanManager(tranche.lender).loanRepayment( tranche.loanId, tranche.principalAmount, tranche.aprBps, tranche.accruedInterest, loan.protocolFee, tranche.startTime ); } unchecked { ++i; } } if (withProtocolFee) { asset.safeTransferFrom(loan.borrower, _protocolFee.recipient, totalProtocolFee); } return (totalRepayment, totalProtocolFee); } /// @notice Process a series of offers and return the loan ID, offer IDs, loan (built from such offers) and total fee. /// @param _borrower The borrower of the loan. /// @param _principalReceiver The receiver of the principal. /// @param _principalAddress The principal address of the loan. /// @param _nftCollateralAddress The NFT collateral address of the loan. /// @param _tokenId The token ID of the loan. /// @param _duration The duration of the loan. /// @param _offerExecution The offer execution. /// @return loanId The loan ID. /// @return offerIds The offer IDs. /// @return loan The loan. /// @return totalFee The total fee. function _processOffersFromExecutionData( address _borrower, address _principalReceiver, address _principalAddress, address _nftCollateralAddress, uint256 _tokenId, uint256 _duration, OfferExecution[] calldata _offerExecution ) private returns (uint256, uint256[] memory, Loan memory, uint256) { Tranche[] memory tranche = new Tranche[](_offerExecution.length); uint256[] memory offerIds = new uint256[](_offerExecution.length); uint256 totalAmount; uint256 loanId = _getAndSetNewLoanId(); ProtocolFee memory protocolFee = _protocolFee; LoanOffer calldata offer; uint256 totalFee; uint256 totalAmountWithMaxInterest; uint256 minAmount = type(uint256).max; uint256 totalOffers = _offerExecution.length; for (uint256 i = 0; i < totalOffers;) { OfferExecution calldata thisOfferExecution = _offerExecution[i]; offer = thisOfferExecution.offer; _validateOfferExecution( thisOfferExecution, _tokenId, offer.lender, _duration, thisOfferExecution.lenderOfferSignature, protocolFee.fraction, totalAmount ); uint256 amount = thisOfferExecution.amount; if (amount < minAmount) { minAmount = amount; } address lender = offer.lender; _checkOffer(offer, _principalAddress, _nftCollateralAddress, totalAmountWithMaxInterest); /// @dev Please note that we can now have many tranches with same `loanId`. tranche[i] = Tranche(loanId, totalAmount, amount, lender, 0, block.timestamp, offer.aprBps); unchecked { totalAmount += amount; totalAmountWithMaxInterest += amount + amount.getInterest(offer.aprBps, _duration); } uint256 fee; unchecked { fee = offer.fee.mulDivUp(amount, offer.principalAmount); totalFee += fee; } _handleProtocolFeeForFee( offer.principalAddress, lender, fee.mulDivUp(protocolFee.fraction, _PRECISION), protocolFee.recipient ); ERC20(offer.principalAddress).safeTransferFrom(lender, _principalReceiver, amount - fee); if (offer.capacity != 0) { unchecked { _used[lender][offer.offerId] += amount; } } else { isOfferCancelled[lender][offer.offerId] = true; } offerIds[i] = offer.offerId; unchecked { ++i; } } if (minAmount < _getMinTranchePrincipal(totalAmount)) { revert InvalidTrancheError(); } Loan memory loan = Loan( _borrower, _tokenId, _nftCollateralAddress, _principalAddress, totalAmount, block.timestamp, _duration, tranche, protocolFee.fraction ); return (loanId, offerIds, loan, totalFee); } function _addNewTranche( uint256 _newLoanId, IMultiSourceLoan.Loan memory _loan, IMultiSourceLoan.RenegotiationOffer calldata _renegotiationOffer ) private view returns (IMultiSourceLoan.Loan memory) { if (_renegotiationOffer.principalAmount < _getMinTranchePrincipal(_loan.principalAmount)) { revert InvalidTrancheError(); } uint256 newTrancheIndex = _loan.tranche.length; IMultiSourceLoan.Tranche[] memory tranches = new IMultiSourceLoan.Tranche[](newTrancheIndex + 1); /// @dev Copy old tranches for (uint256 i = 0; i < newTrancheIndex;) { tranches[i] = _loan.tranche[i]; unchecked { ++i; } } tranches[newTrancheIndex] = IMultiSourceLoan.Tranche( _newLoanId, _loan.principalAmount, _renegotiationOffer.principalAmount, _renegotiationOffer.lender, 0, block.timestamp, _renegotiationOffer.aprBps ); _loan.tranche = tranches; unchecked { _loan.principalAmount += _renegotiationOffer.principalAmount; } return _loan; } /// @notice Check a signature is valid given a hash and signer. /// @dev Comply with IERC1271 and EIP-712. function _checkSignature(address _signer, bytes32 _hash, bytes calldata _signature) private view { bytes32 typedDataHash = DOMAIN_SEPARATOR().toTypedDataHash(_hash); if (_signer.code.length != 0) { if (IERC1271(_signer).isValidSignature(typedDataHash, _signature) != MAGICVALUE_1271) { revert InvalidSignatureError(); } } else { address recovered = typedDataHash.recover(_signature); if (_signer != recovered) { revert InvalidSignatureError(); } } } /// @dev Check whether an offer is strictly better than a tranche function _checkStrictlyBetter( uint256 _offerPrincipalAmount, uint256 _loanPrincipalAmount, uint256 _offerEndTime, uint256 _loanEndTime, uint256 _offerAprBps, uint256 _loanAprBps, uint256 _offerFee ) internal view { uint256 minImprovementApr = _minImprovementApr; /// @dev If principal is increased, then we need to check net daily interest is better. /// interestDelta = (_loanAprBps * _loanPrincipalAmount - _offerAprBps * _offerPrincipalAmount) /// We already checked that all tranches are strictly better. /// We check that the duration is not decreased or the offer charges a fee. if ( ( (_offerPrincipalAmount - _loanPrincipalAmount != 0) && ( (_loanAprBps * _loanPrincipalAmount - _offerAprBps * _offerPrincipalAmount).mulDivDown( _PRECISION, _loanAprBps * _loanPrincipalAmount ) < minImprovementApr ) ) || (_offerFee != 0) || (_offerEndTime < _loanEndTime) ) { revert NotStrictlyImprovedError(); } } }
// SPDX-License-Identifier: GPL-2.0-or-later /* * @title Solidity Bytes Arrays Utils * @author Gonçalo Sá <[email protected]> * * @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity. * The library lets you concatenate, slice and type cast bytes arrays both in memory and storage. */ pragma solidity ^0.8.21; library BytesLib { function slice(bytes memory _bytes, uint256 _start, uint256 _length) internal pure returns (bytes memory) { require(_length + 31 >= _length, "slice_overflow"); require(_start + _length >= _start, "slice_overflow"); require(_bytes.length >= _start + _length, "slice_outOfBounds"); bytes memory tempBytes; assembly { switch iszero(_length) case 0 { // Get a location of some free memory and store it in tempBytes as // Solidity does for memory variables. tempBytes := mload(0x40) // The first word of the slice result is potentially a partial // word read from the original array. To read it, we calculate // the length of that partial word and start copying that many // bytes into the array. The first word we copy will start with // data we don't care about, but the last `lengthmod` bytes will // land at the beginning of the contents of the new array. When // we're done copying, we overwrite the full first word with // the actual length of the slice. let lengthmod := and(_length, 31) // The multiplication in the next line is necessary // because when slicing multiples of 32 bytes (lengthmod == 0) // the following copy loop was copying the origin's length // and then ending prematurely not copying everything it should. let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) let end := add(mc, _length) for { // The multiplication in the next line has the same exact purpose // as the one above. let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) } lt(mc, end) { mc := add(mc, 0x20) cc := add(cc, 0x20) } { mstore(mc, mload(cc)) } mstore(tempBytes, _length) //update free-memory pointer //allocating the array padded to 32 bytes like the compiler does now mstore(0x40, and(add(mc, 31), not(31))) } //if we want a zero-length slice let's just return a zero-length array default { tempBytes := mload(0x40) //zero out the 32 bytes slice we are about to return //we need to do it because Solidity does not garbage collect mstore(tempBytes, 0) mstore(0x40, add(tempBytes, 0x20)) } } return tempBytes; } function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) { require(_start + 20 >= _start, "toAddress_overflow"); require(_bytes.length >= _start + 20, "toAddress_outOfBounds"); address tempAddress; assembly { tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000) } return tempAddress; } function toUint24(bytes memory _bytes, uint256 _start) internal pure returns (uint24) { require(_start + 3 >= _start, "toUint24_overflow"); require(_bytes.length >= _start + 3, "toUint24_outOfBounds"); uint24 tempUint; assembly { tempUint := mload(add(add(_bytes, 0x3), _start)) } return tempUint; } }
// SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.21; import "@solmate/auth/Owned.sol"; import "@solmate/utils/ReentrancyGuard.sol"; import "./InputChecker.sol"; /// @title AddressManager /// @notice A contract that handles a whitelist of addresses and their indexes. /// @dev We assume no more than 65535 addresses will be added to the directory. contract AddressManager is Owned, ReentrancyGuard { using InputChecker for address; event AddressAdded(address address_added); event AddressRemovedFromWhitelist(address address_removed); event AddressWhitelisted(address address_whitelisted); error AddressAlreadyAddedError(address _address); error AddressNotAddedError(address _address); mapping(address => uint16) private _directory; mapping(uint16 => address) private _inverseDirectory; mapping(address => bool) private _whitelist; uint16 private _lastAdded; constructor(address[] memory _original) Owned(tx.origin) { uint256 total = _original.length; for (uint256 i; i < total;) { _add(_original[i]); unchecked { ++i; } } } /// @notice Adds an address to the directory. If it already exists, /// reverts. It assumes it's whitelisted. /// @param _entry The address to add. /// @return The index of the address in the directory. function add(address _entry) external payable onlyOwner returns (uint16) { return _add(_entry); } /// @notice Whitelist an address that's already part of the directory. /// @param _entry The address to whitelist. function addToWhitelist(address _entry) external payable onlyOwner { if (_directory[_entry] == 0) { revert AddressNotAddedError(_entry); } _whitelist[_entry] = true; emit AddressWhitelisted(_entry); } /// @notice Removes an address from the whitelist. We still keep it /// in the directory since this mapping is relevant across time. /// @param _entry The address to remove from the whitelist. function removeFromWhitelist(address _entry) external payable onlyOwner { _whitelist[_entry] = false; emit AddressRemovedFromWhitelist(_entry); } /// @param _address The address to get the index for. /// @return The index for a given address. function addressToIndex(address _address) external view returns (uint16) { return _directory[_address]; } /// @param _index The index to get the address for. /// @return The address for a given index. function indexToAddress(uint16 _index) external view returns (address) { return _inverseDirectory[_index]; } /// @param _entry The address to check if it's whitelisted. /// @return Whether the address is whitelisted or not. function isWhitelisted(address _entry) external view returns (bool) { return _whitelist[_entry]; } function _add(address _entry) private returns (uint16) { _entry.checkNotZero(); if (_directory[_entry] != 0) { revert AddressAlreadyAddedError(_entry); } unchecked { ++_lastAdded; } _directory[_entry] = _lastAdded; _inverseDirectory[_lastAdded] = _entry; _whitelist[_entry] = true; emit AddressAdded(_entry); return _lastAdded; } }
// SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.21; /// @title InputChecker /// @author Florida St /// @notice Some basic input checks. library InputChecker { error AddressZeroError(); function checkNotZero(address _address) internal pure { if (_address == address(0)) { revert AddressZeroError(); } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import {SafeTransferLib} from "@solmate/utils/SafeTransferLib.sol"; import {MessageHashUtils} from "@openzeppelin/utils/cryptography/MessageHashUtils.sol"; import {ERC20} from "@solmate/tokens/ERC20.sol"; import {ERC721} from "@solmate/tokens/ERC721.sol"; import {ECDSA} from "@openzeppelin/utils/cryptography/ECDSA.sol"; import {ITradeMarketplace} from "../../interfaces/callbacks/ITradeMarketplace.sol"; import {Hash} from "../utils/Hash.sol"; contract TradeMarketplace is ITradeMarketplace { using Hash for ITradeMarketplace.Order; using MessageHashUtils for bytes32; using SafeTransferLib for ERC20; mapping(address => uint256) public minNonce; mapping(address => mapping(uint256 => bool)) public cancelled; /// @notice Used in compliance with EIP712 uint256 internal immutable INITIAL_CHAIN_ID; bytes32 public immutable INITIAL_DOMAIN_SEPARATOR; string public name; bytes public constant VERSION = "1"; constructor(string memory _name) { name = _name; INITIAL_CHAIN_ID = block.chainid; INITIAL_DOMAIN_SEPARATOR = _computeDomainSeparator(); } function executeOrder(Order memory order) public override { _executeOrder(order, msg.sender); } function _executeOrder(Order memory order, address taker) internal { _isValidOrder(order); ERC20 currency = ERC20(order.currency); if (order.isAsk) { _transferERC20(currency, taker, order.maker, order.price); ERC721(order.collection).safeTransferFrom(order.maker, taker, order.tokenId); } else { _transferERC20(currency, order.maker, taker, order.price); ERC721(order.collection).safeTransferFrom(taker, order.maker, order.tokenId); } emit OrderExecuted( order.maker, taker, order.collection, order.tokenId, order.currency, order.price, order.nonce, order.expiration, order.isAsk ); } function cancelOrder(uint256 nonce) public override { cancelled[msg.sender][nonce] = true; } function cancelAllOrders(uint256 nonce) public override { address user = msg.sender; uint256 currentMinNonce = minNonce[user]; if (currentMinNonce >= nonce) { revert LowNonceError(user, nonce, currentMinNonce); } minNonce[user] = nonce; emit AllOrdersCancelled(user, nonce); } function orderHash(Order memory order) public pure override returns (bytes32) { return Hash.hash(order); } function isOrderCancelled(Order memory order) public view override returns (bool) { return _isOrderCancelled(order); } function _isOrderCancelled(Order memory order) private view returns (bool) { return cancelled[order.maker][order.nonce] || minNonce[order.maker] > order.nonce; } function _isValidOrder(Order memory order) private view { _hasValidSignature(order); if (order.expiration < block.timestamp) { revert OrderExpired(); } if (_isOrderCancelled(order)) { revert OrderCancelled(); } if (order.taker != address(0) && order.taker != msg.sender) { revert InvalidTaker(); } } function _transferERC20(ERC20 token, address from, address to, uint256 amount) private { if (from == address(this)) { token.safeTransfer(to, amount); } else { token.safeTransferFrom(from, to, amount); } } function _hasValidSignature(Order memory order) private view { bytes32 hash = DOMAIN_SEPARATOR().toTypedDataHash(Hash.hash(order)); address signer = ECDSA.recover(hash, order.signature); if (signer != order.maker) { revert InvalidSignature(); } } /// @notice Get the domain separator requried to comply with EIP-712. function DOMAIN_SEPARATOR() public view returns (bytes32) { return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : _computeDomainSeparator(); } /// @notice Compute domain separator for EIP-712. /// @return The domain separator. function _computeDomainSeparator() private view returns (bytes32) { return keccak256( abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak256(bytes(name)), keccak256(VERSION), block.chainid, address(this) ) ); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.21; interface IAaveFlashLoanReceiver { function executeOperation( address[] calldata assets, uint256[] calldata amounts, uint256[] calldata premiums, address initiator, bytes calldata params ) external returns (bool); }
// SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.20; import "./IBaseLoan.sol"; interface IMultiSourceLoan { /// @param executionData Execution data. /// @param lender Lender address. /// @param borrower Address that owns the NFT and will take over the loan. /// @param lenderOfferSignature Signature of the offer (signed by lender). /// @param borrowerOfferSignature Signature of the offer (signed by borrower). /// @param callbackData Whether to call the afterPrincipalTransfer callback struct LoanExecutionData { IBaseLoan.ExecutionData executionData; address lender; address borrower; bytes lenderOfferSignature; bytes borrowerOfferSignature; } /// @param loanId Loan ID. /// @param callbackData Whether to call the afterNFTTransfer callback /// @param shouldDelegate Whether to delegate ownership of the NFT (avoid seaport flags). struct SignableRepaymentData { uint256 loanId; bytes callbackData; bool shouldDelegate; } /// @param loan Loan. /// @param borrowerLoanSignature Signature of the loan (signed by borrower). struct LoanRepaymentData { SignableRepaymentData data; Loan loan; bytes borrowerSignature; } /// @notice When a loan is initiated, there's one source, the original lender. After each refinance, /// a new source is created to represent the new lender, and potentially others dissapear (if a tranche /// is fully refinanced) /// @dev No need to have principal address here since it's the same across all, so it can live in the Loan. /// @param loanId Loan ID. /// @param lender Lender for this given source. /// @param principalAmount Principal Amount. /// @param accruedInterest Accrued Interest. /// @param startTime Start Time. Either the time at which the loan initiated / was refinanced. /// @param aprBps APR in basis points. struct Source { uint256 loanId; address lender; uint256 principalAmount; uint256 accruedInterest; uint256 startTime; uint256 aprBps; } /// @dev Principal Amount is equal to the sum of all sources principalAmount. /// We keep it for caching purposes. Since we are not saving this on chain but the hash, /// it does not have a huge impact on gas. /// @param borrower Borrower. /// @param nftCollateralTokenId NFT Collateral Token ID. /// @param nftCollateralAddress NFT Collateral Address. /// @param principalAddress Principal Address. /// @param principalAmount Principal Amount. /// @param startTime Start Time. /// @param duration Duration. /// @param source Sources struct Loan { address borrower; uint256 nftCollateralTokenId; address nftCollateralAddress; address principalAddress; uint256 principalAmount; uint256 startTime; uint256 duration; Source[] source; } struct RenegotiationOffer { uint256 renegotiationId; uint256 loanId; address lender; uint256 fee; uint256[] targetPrincipal; uint256 principalAmount; uint256 aprBps; uint256 expirationTime; uint256 duration; } /// @notice Call by the borrower when emiting a new loan. /// @param _executionData Loan execution data. /// @return loanId Loan ID. /// @return loan Loan. function emitLoan(LoanExecutionData calldata _executionData) external returns (uint256, Loan memory); /// @notice Refinance whole loan (leaving just one source). /// @param _renegotiationOffer Offer to refinance a loan. /// @param _loan Current loan. /// @param _renegotiationOfferSignature Signature of the offer. /// @return loanId New Loan Id, New Loan. function refinanceFull( RenegotiationOffer calldata _renegotiationOffer, Loan memory _loan, bytes calldata _renegotiationOfferSignature ) external returns (uint256, Loan memory); /// @notice Refinance a loan partially. It can only be called by the new lender /// (they are always a strict improvement on apr). /// @param _renegotiationOffer Offer to refinance a loan partially. /// @param _loan Current loan. /// @return loanId New Loan Id, New Loan. function refinancePartial(RenegotiationOffer calldata _renegotiationOffer, Loan memory _loan) external returns (uint256, Loan memory); /// @notice Repay loan. Interest is calculated pro-rata based on time. Lender is defined by nft ownership. /// @param _repaymentData Repayment data. function repayLoan(LoanRepaymentData calldata _repaymentData) external; /// @notice Call when a loan is past its due date. /// @param _loanId Loan ID. /// @param _loan Loan. /// @return Liquidation Struct of the liquidation. function liquidateLoan(uint256 _loanId, Loan calldata _loan) external returns (bytes memory); /// @return maxSources Max sources per loan. function getMaxSources() external view returns (uint256); /// @notice Update the maximum number of sources per loan. /// @param maxSources Maximum number of sources. function setMaxSources(uint256 maxSources) external; /// @notice Set min lock period (in BPS). /// @param _minLockPeriod Min lock period. function setMinLockPeriod(uint256 _minLockPeriod) external; /// @notice Get min lock period (in BPS). /// @return minLockPeriod Min lock period. function getMinLockPeriod() external view returns (uint256); /// @notice Get delegation registry. /// @return delegateRegistry Delegate registry. function getDelegateRegistry() external view returns (address); /// @notice Update delegation registry. /// @param _newDelegationRegistry Delegation registry. function setDelegateRegistry(address _newDelegationRegistry) external; /// @notice Delegate ownership. /// @param _loanId Loan ID. /// @param _loan Loan. /// @param _rights Delegation Rights. Empty for all. /// @param _delegate Delegate address. /// @param _value True if delegate, false if undelegate. function delegate(uint256 _loanId, Loan calldata _loan, address _delegate, bytes32 _rights, bool _value) external; /// @notice Anyone can reveke a delegation on an NFT that's no longer in escrow. /// @param _delegate Delegate address. /// @param _collection Collection address. /// @param _tokenId Token ID. function revokeDelegate(address _delegate, address _collection, uint256 _tokenId) external; /// @notice Get Flash Action Contract. /// @return flashActionContract Flash Action Contract. function getFlashActionContract() external view returns (address); /// @notice Update Flash Action Contract. /// @param _newFlashActionContract Flash Action Contract. function setFlashActionContract(address _newFlashActionContract) external; /// @notice Get Loan Hash. /// @param _loanId Loan ID. /// @return loanHash Loan Hash. function getLoanHash(uint256 _loanId) external view returns (bytes32); /// @notice Transfer NFT to the flash action contract (expected use cases here are for airdrops and similar scenarios). /// The flash action contract would implement specific interactions with given contracts. /// Only the the borrower can call this function for a given loan. By the end of the transaction, the NFT must have /// been returned to escrow. /// @param _loanId Loan ID. /// @param _loan Loan. /// @param _target Target address for the flash action contract to interact with. /// @param _data Data to be passed to be passed to the ultimate contract. function executeFlashAction(uint256 _loanId, Loan calldata _loan, address _target, bytes calldata _data) external; /// @notice Extend loan duration. Can only be called by the lender on loans that have just one source. /// @param _loanId Loan ID. /// @param _loan Loan. /// @param _extension Extension in seconds. /// @return newLoanId New Loan Id /// @return newLoan New Loan. function extendLoan(uint256 _loanId, Loan memory _loan, uint256 _extension) external returns (uint256, Loan memory); /// @notice Called by the liquidator for accounting purposes. /// @param _loanId The id of the loan. /// @param _loan The loan object. function loanLiquidated(uint256 _loanId, Loan calldata _loan) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {ISignatureTransfer} from "./ISignatureTransfer.sol"; import {IAllowanceTransfer} from "./IAllowanceTransfer.sol"; /// @notice Permit2 handles signature-based transfers in SignatureTransfer and allowance-based transfers in AllowanceTransfer. /// @dev Users must approve Permit2 before calling any of the transfer functions. interface IPermit2 is ISignatureTransfer, IAllowanceTransfer { // IPermit2 unifies the two interfaces so users have maximal flexibility with their approval. }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; /// @notice Modern and gas efficient ERC20 + EIP-2612 implementation. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol) /// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol) /// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it. abstract contract ERC20 { /*////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ event Transfer(address indexed from, address indexed to, uint256 amount); event Approval(address indexed owner, address indexed spender, uint256 amount); /*////////////////////////////////////////////////////////////// METADATA STORAGE //////////////////////////////////////////////////////////////*/ string public name; string public symbol; uint8 public immutable decimals; /*////////////////////////////////////////////////////////////// ERC20 STORAGE //////////////////////////////////////////////////////////////*/ uint256 public totalSupply; mapping(address => uint256) public balanceOf; mapping(address => mapping(address => uint256)) public allowance; /*////////////////////////////////////////////////////////////// EIP-2612 STORAGE //////////////////////////////////////////////////////////////*/ uint256 internal immutable INITIAL_CHAIN_ID; bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR; mapping(address => uint256) public nonces; /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ constructor( string memory _name, string memory _symbol, uint8 _decimals ) { name = _name; symbol = _symbol; decimals = _decimals; INITIAL_CHAIN_ID = block.chainid; INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator(); } /*////////////////////////////////////////////////////////////// ERC20 LOGIC //////////////////////////////////////////////////////////////*/ function approve(address spender, uint256 amount) public virtual returns (bool) { allowance[msg.sender][spender] = amount; emit Approval(msg.sender, spender, amount); return true; } function transfer(address to, uint256 amount) public virtual returns (bool) { balanceOf[msg.sender] -= amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(msg.sender, to, amount); return true; } function transferFrom( address from, address to, uint256 amount ) public virtual returns (bool) { uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; balanceOf[from] -= amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(from, to, amount); return true; } /*////////////////////////////////////////////////////////////// EIP-2612 LOGIC //////////////////////////////////////////////////////////////*/ function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) public virtual { require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED"); // Unchecked because the only math done is incrementing // the owner's nonce which cannot realistically overflow. unchecked { address recoveredAddress = ecrecover( keccak256( abi.encodePacked( "\x19\x01", DOMAIN_SEPARATOR(), keccak256( abi.encode( keccak256( "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" ), owner, spender, value, nonces[owner]++, deadline ) ) ) ), v, r, s ); require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER"); allowance[recoveredAddress][spender] = value; } emit Approval(owner, spender, value); } function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator(); } function computeDomainSeparator() internal view virtual returns (bytes32) { return keccak256( abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak256(bytes(name)), keccak256("1"), block.chainid, address(this) ) ); } /*////////////////////////////////////////////////////////////// INTERNAL MINT/BURN LOGIC //////////////////////////////////////////////////////////////*/ function _mint(address to, uint256 amount) internal virtual { totalSupply += amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(address(0), to, amount); } function _burn(address from, uint256 amount) internal virtual { balanceOf[from] -= amount; // Cannot underflow because a user's balance // will never be larger than the total supply. unchecked { totalSupply -= amount; } emit Transfer(from, address(0), amount); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @title IPoolAddressesProvider * @author Aave * @notice Defines the basic interface for a Pool Addresses Provider. */ interface IPoolAddressesProvider { /** * @dev Emitted when the market identifier is updated. * @param oldMarketId The old id of the market * @param newMarketId The new id of the market */ event MarketIdSet(string indexed oldMarketId, string indexed newMarketId); /** * @dev Emitted when the pool is updated. * @param oldAddress The old address of the Pool * @param newAddress The new address of the Pool */ event PoolUpdated(address indexed oldAddress, address indexed newAddress); /** * @dev Emitted when the pool configurator is updated. * @param oldAddress The old address of the PoolConfigurator * @param newAddress The new address of the PoolConfigurator */ event PoolConfiguratorUpdated(address indexed oldAddress, address indexed newAddress); /** * @dev Emitted when the price oracle is updated. * @param oldAddress The old address of the PriceOracle * @param newAddress The new address of the PriceOracle */ event PriceOracleUpdated(address indexed oldAddress, address indexed newAddress); /** * @dev Emitted when the ACL manager is updated. * @param oldAddress The old address of the ACLManager * @param newAddress The new address of the ACLManager */ event ACLManagerUpdated(address indexed oldAddress, address indexed newAddress); /** * @dev Emitted when the ACL admin is updated. * @param oldAddress The old address of the ACLAdmin * @param newAddress The new address of the ACLAdmin */ event ACLAdminUpdated(address indexed oldAddress, address indexed newAddress); /** * @dev Emitted when the price oracle sentinel is updated. * @param oldAddress The old address of the PriceOracleSentinel * @param newAddress The new address of the PriceOracleSentinel */ event PriceOracleSentinelUpdated(address indexed oldAddress, address indexed newAddress); /** * @dev Emitted when the pool data provider is updated. * @param oldAddress The old address of the PoolDataProvider * @param newAddress The new address of the PoolDataProvider */ event PoolDataProviderUpdated(address indexed oldAddress, address indexed newAddress); /** * @dev Emitted when a new proxy is created. * @param id The identifier of the proxy * @param proxyAddress The address of the created proxy contract * @param implementationAddress The address of the implementation contract */ event ProxyCreated( bytes32 indexed id, address indexed proxyAddress, address indexed implementationAddress ); /** * @dev Emitted when a new non-proxied contract address is registered. * @param id The identifier of the contract * @param oldAddress The address of the old contract * @param newAddress The address of the new contract */ event AddressSet(bytes32 indexed id, address indexed oldAddress, address indexed newAddress); /** * @dev Emitted when the implementation of the proxy registered with id is updated * @param id The identifier of the contract * @param proxyAddress The address of the proxy contract * @param oldImplementationAddress The address of the old implementation contract * @param newImplementationAddress The address of the new implementation contract */ event AddressSetAsProxy( bytes32 indexed id, address indexed proxyAddress, address oldImplementationAddress, address indexed newImplementationAddress ); /** * @notice Returns the id of the Aave market to which this contract points to. * @return The market id */ function getMarketId() external view returns (string memory); /** * @notice Associates an id with a specific PoolAddressesProvider. * @dev This can be used to create an onchain registry of PoolAddressesProviders to * identify and validate multiple Aave markets. * @param newMarketId The market id */ function setMarketId(string calldata newMarketId) external; /** * @notice Returns an address by its identifier. * @dev The returned address might be an EOA or a contract, potentially proxied * @dev It returns ZERO if there is no registered address with the given id * @param id The id * @return The address of the registered for the specified id */ function getAddress(bytes32 id) external view returns (address); /** * @notice General function to update the implementation of a proxy registered with * certain `id`. If there is no proxy registered, it will instantiate one and * set as implementation the `newImplementationAddress`. * @dev IMPORTANT Use this function carefully, only for ids that don't have an explicit * setter function, in order to avoid unexpected consequences * @param id The id * @param newImplementationAddress The address of the new implementation */ function setAddressAsProxy(bytes32 id, address newImplementationAddress) external; /** * @notice Sets an address for an id replacing the address saved in the addresses map. * @dev IMPORTANT Use this function carefully, as it will do a hard replacement * @param id The id * @param newAddress The address to set */ function setAddress(bytes32 id, address newAddress) external; /** * @notice Returns the address of the Pool proxy. * @return The Pool proxy address */ function getPool() external view returns (address); /** * @notice Updates the implementation of the Pool, or creates a proxy * setting the new `pool` implementation when the function is called for the first time. * @param newPoolImpl The new Pool implementation */ function setPoolImpl(address newPoolImpl) external; /** * @notice Returns the address of the PoolConfigurator proxy. * @return The PoolConfigurator proxy address */ function getPoolConfigurator() external view returns (address); /** * @notice Updates the implementation of the PoolConfigurator, or creates a proxy * setting the new `PoolConfigurator` implementation when the function is called for the first time. * @param newPoolConfiguratorImpl The new PoolConfigurator implementation */ function setPoolConfiguratorImpl(address newPoolConfiguratorImpl) external; /** * @notice Returns the address of the price oracle. * @return The address of the PriceOracle */ function getPriceOracle() external view returns (address); /** * @notice Updates the address of the price oracle. * @param newPriceOracle The address of the new PriceOracle */ function setPriceOracle(address newPriceOracle) external; /** * @notice Returns the address of the ACL manager. * @return The address of the ACLManager */ function getACLManager() external view returns (address); /** * @notice Updates the address of the ACL manager. * @param newAclManager The address of the new ACLManager */ function setACLManager(address newAclManager) external; /** * @notice Returns the address of the ACL admin. * @return The address of the ACL admin */ function getACLAdmin() external view returns (address); /** * @notice Updates the address of the ACL admin. * @param newAclAdmin The address of the new ACL admin */ function setACLAdmin(address newAclAdmin) external; /** * @notice Returns the address of the price oracle sentinel. * @return The address of the PriceOracleSentinel */ function getPriceOracleSentinel() external view returns (address); /** * @notice Updates the address of the price oracle sentinel. * @param newPriceOracleSentinel The address of the new PriceOracleSentinel */ function setPriceOracleSentinel(address newPriceOracleSentinel) external; /** * @notice Returns the address of the data provider. * @return The address of the DataProvider */ function getPoolDataProvider() external view returns (address); /** * @notice Updates the address of the data provider. * @param newDataProvider The address of the new DataProvider */ function setPoolDataProvider(address newDataProvider) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; library DataTypes { /** * This exists specifically to maintain the `getReserveData()` interface, since the new, internal * `ReserveData` struct includes the reserve's `virtualUnderlyingBalance`. */ struct ReserveDataLegacy { //stores the reserve configuration ReserveConfigurationMap configuration; //the liquidity index. Expressed in ray uint128 liquidityIndex; //the current supply rate. Expressed in ray uint128 currentLiquidityRate; //variable borrow index. Expressed in ray uint128 variableBorrowIndex; //the current variable borrow rate. Expressed in ray uint128 currentVariableBorrowRate; // DEPRECATED on v3.2.0 uint128 currentStableBorrowRate; //timestamp of last update uint40 lastUpdateTimestamp; //the id of the reserve. Represents the position in the list of the active reserves uint16 id; //aToken address address aTokenAddress; // DEPRECATED on v3.2.0 address stableDebtTokenAddress; //variableDebtToken address address variableDebtTokenAddress; //address of the interest rate strategy address interestRateStrategyAddress; //the current treasury balance, scaled uint128 accruedToTreasury; //the outstanding unbacked aTokens minted through the bridging feature uint128 unbacked; //the outstanding debt borrowed against this asset in isolation mode uint128 isolationModeTotalDebt; } struct ReserveData { //stores the reserve configuration ReserveConfigurationMap configuration; //the liquidity index. Expressed in ray uint128 liquidityIndex; //the current supply rate. Expressed in ray uint128 currentLiquidityRate; //variable borrow index. Expressed in ray uint128 variableBorrowIndex; //the current variable borrow rate. Expressed in ray uint128 currentVariableBorrowRate; // DEPRECATED on v3.2.0 uint128 __deprecatedStableBorrowRate; //timestamp of last update uint40 lastUpdateTimestamp; //the id of the reserve. Represents the position in the list of the active reserves uint16 id; //timestamp until when liquidations are not allowed on the reserve, if set to past liquidations will be allowed uint40 liquidationGracePeriodUntil; //aToken address address aTokenAddress; // DEPRECATED on v3.2.0 address __deprecatedStableDebtTokenAddress; //variableDebtToken address address variableDebtTokenAddress; //address of the interest rate strategy address interestRateStrategyAddress; //the current treasury balance, scaled uint128 accruedToTreasury; //the outstanding unbacked aTokens minted through the bridging feature uint128 unbacked; //the outstanding debt borrowed against this asset in isolation mode uint128 isolationModeTotalDebt; //the amount of underlying accounted for by the protocol uint128 virtualUnderlyingBalance; } struct ReserveConfigurationMap { //bit 0-15: LTV //bit 16-31: Liq. threshold //bit 32-47: Liq. bonus //bit 48-55: Decimals //bit 56: reserve is active //bit 57: reserve is frozen //bit 58: borrowing is enabled //bit 59: DEPRECATED: stable rate borrowing enabled //bit 60: asset is paused //bit 61: borrowing in isolation mode is enabled //bit 62: siloed borrowing enabled //bit 63: flashloaning enabled //bit 64-79: reserve factor //bit 80-115: borrow cap in whole tokens, borrowCap == 0 => no cap //bit 116-151: supply cap in whole tokens, supplyCap == 0 => no cap //bit 152-167: liquidation protocol fee //bit 168-175: DEPRECATED: eMode category //bit 176-211: unbacked mint cap in whole tokens, unbackedMintCap == 0 => minting disabled //bit 212-251: debt ceiling for isolation mode with (ReserveConfiguration::DEBT_CEILING_DECIMALS) decimals //bit 252: virtual accounting is enabled for the reserve //bit 253-255 unused uint256 data; } struct UserConfigurationMap { /** * @dev Bitmap of the users collaterals and borrows. It is divided in pairs of bits, one pair per asset. * The first bit indicates if an asset is used as collateral by the user, the second whether an * asset is borrowed by the user. */ uint256 data; } // DEPRECATED: kept for backwards compatibility, might be removed in a future version struct EModeCategoryLegacy { // each eMode category has a custom ltv and liquidation threshold uint16 ltv; uint16 liquidationThreshold; uint16 liquidationBonus; // DEPRECATED address priceSource; string label; } struct CollateralConfig { uint16 ltv; uint16 liquidationThreshold; uint16 liquidationBonus; } struct EModeCategoryBaseConfiguration { uint16 ltv; uint16 liquidationThreshold; uint16 liquidationBonus; string label; } struct EModeCategory { // each eMode category has a custom ltv and liquidation threshold uint16 ltv; uint16 liquidationThreshold; uint16 liquidationBonus; uint128 collateralBitmap; string label; uint128 borrowableBitmap; } enum InterestRateMode { NONE, __DEPRECATED, VARIABLE } struct ReserveCache { uint256 currScaledVariableDebt; uint256 nextScaledVariableDebt; uint256 currLiquidityIndex; uint256 nextLiquidityIndex; uint256 currVariableBorrowIndex; uint256 nextVariableBorrowIndex; uint256 currLiquidityRate; uint256 currVariableBorrowRate; uint256 reserveFactor; ReserveConfigurationMap reserveConfiguration; address aTokenAddress; address variableDebtTokenAddress; uint40 reserveLastUpdateTimestamp; } struct ExecuteLiquidationCallParams { uint256 reservesCount; uint256 debtToCover; address collateralAsset; address debtAsset; address user; bool receiveAToken; address priceOracle; uint8 userEModeCategory; address priceOracleSentinel; } struct ExecuteSupplyParams { address asset; uint256 amount; address onBehalfOf; uint16 referralCode; } struct ExecuteBorrowParams { address asset; address user; address onBehalfOf; uint256 amount; InterestRateMode interestRateMode; uint16 referralCode; bool releaseUnderlying; uint256 reservesCount; address oracle; uint8 userEModeCategory; address priceOracleSentinel; } struct ExecuteRepayParams { address asset; uint256 amount; InterestRateMode interestRateMode; address onBehalfOf; bool useATokens; } struct ExecuteWithdrawParams { address asset; uint256 amount; address to; uint256 reservesCount; address oracle; uint8 userEModeCategory; } struct ExecuteSetUserEModeParams { uint256 reservesCount; address oracle; uint8 categoryId; } struct FinalizeTransferParams { address asset; address from; address to; uint256 amount; uint256 balanceFromBefore; uint256 balanceToBefore; uint256 reservesCount; address oracle; uint8 fromEModeCategory; } struct FlashloanParams { address receiverAddress; address[] assets; uint256[] amounts; uint256[] interestRateModes; address onBehalfOf; bytes params; uint16 referralCode; uint256 flashLoanPremiumToProtocol; uint256 flashLoanPremiumTotal; uint256 reservesCount; address addressesProvider; address pool; uint8 userEModeCategory; bool isAuthorizedFlashBorrower; } struct FlashloanSimpleParams { address receiverAddress; address asset; uint256 amount; bytes params; uint16 referralCode; uint256 flashLoanPremiumToProtocol; uint256 flashLoanPremiumTotal; } struct FlashLoanRepaymentParams { uint256 amount; uint256 totalPremium; uint256 flashLoanPremiumToProtocol; address asset; address receiverAddress; uint16 referralCode; } struct CalculateUserAccountDataParams { UserConfigurationMap userConfig; uint256 reservesCount; address user; address oracle; uint8 userEModeCategory; } struct ValidateBorrowParams { ReserveCache reserveCache; UserConfigurationMap userConfig; address asset; address userAddress; uint256 amount; InterestRateMode interestRateMode; uint256 reservesCount; address oracle; uint8 userEModeCategory; address priceOracleSentinel; bool isolationModeActive; address isolationModeCollateralAddress; uint256 isolationModeDebtCeiling; } struct ValidateLiquidationCallParams { ReserveCache debtReserveCache; uint256 totalDebt; uint256 healthFactor; address priceOracleSentinel; } struct CalculateInterestRatesParams { uint256 unbacked; uint256 liquidityAdded; uint256 liquidityTaken; uint256 totalDebt; uint256 reserveFactor; address reserve; bool usingVirtualBalance; uint256 virtualUnderlyingBalance; } struct InitReserveParams { address asset; address aTokenAddress; address variableDebtAddress; address interestRateStrategyAddress; uint16 reservesCount; uint16 maxNumberReserves; } }
// SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.21; import "./IBaseLoan.sol"; /// @title Multi Source Loan Interface /// @author Florida St /// @notice A multi source loan is one with multiple tranches. interface IMultiSourceLoan { /// @notice Borrowers receive offers that are then validated. /// @dev Setting the nftCollateralTokenId to 0 triggers validation through `validators`. /// @param offerId Offer ID. Used for canceling/setting as executed. /// @param lender Lender of the offer. /// @param fee Origination fee. /// @param capacity Capacity of the offer. /// @param nftCollateralAddress Address of the NFT collateral. /// @param nftCollateralTokenId NFT collateral token ID. /// @param principalAddress Address of the principal. /// @param principalAmount Principal amount of the loan. /// @param aprBps APR in BPS. /// @param expirationTime Expiration time of the offer. /// @param duration Duration of the loan in seconds. /// @param maxSeniorRepayment Max amount of senior capital ahead (principal + interest). /// @param validators Arbitrary contract to validate offers implementing `IBaseOfferValidator`. struct LoanOffer { uint256 offerId; address lender; uint256 fee; uint256 capacity; address nftCollateralAddress; uint256 nftCollateralTokenId; address principalAddress; uint256 principalAmount; uint256 aprBps; uint256 expirationTime; uint256 duration; uint256 maxSeniorRepayment; IBaseLoan.OfferValidator[] validators; } /// @notice Offer + how much will be filled (always <= principalAmount). /// @param offer Offer. /// @param amount Amount to be filled. struct OfferExecution { LoanOffer offer; uint256 amount; bytes lenderOfferSignature; } /// @notice Offer + necessary fields to execute a specific loan. This has a separate expirationTime to avoid /// someone holding an offer and executing much later, without the borrower's awareness. /// @dev It's advised that borrowers only set an expirationTime close to the actual time they will execute the loan /// to avoid replays. /// @param offerExecution List of offers to be filled and amount for each. /// @param tokenId NFT collateral token ID. /// @param amount The amount the borrower is willing to take (must be <= _loanOffer principalAmount) /// @param expirationTime Expiration time of the signed offer by the borrower. /// @param callbackData Data to pass to the callback. struct ExecutionData { OfferExecution[] offerExecution; uint256 tokenId; uint256 duration; uint256 expirationTime; address principalReceiver; bytes callbackData; } /// @param executionData Execution data. /// @param borrower Address that owns the NFT and will take over the loan. /// @param borrowerOfferSignature Signature of the offer (signed by borrower). /// @param callbackData Whether to call the afterPrincipalTransfer callback struct LoanExecutionData { ExecutionData executionData; address borrower; bytes borrowerOfferSignature; } /// @param loanId Loan ID. /// @param callbackData Whether to call the afterNFTTransfer callback /// @param shouldDelegate Whether to delegate ownership of the NFT (avoid seaport flags). struct SignableRepaymentData { uint256 loanId; bytes callbackData; bool shouldDelegate; } /// @param loan Loan. /// @param borrowerLoanSignature Signature of the loan (signed by borrower). struct LoanRepaymentData { SignableRepaymentData data; Loan loan; bytes borrowerSignature; } /// @notice Tranches have different seniority levels. /// @param loanId Loan ID. /// @param floor Amount of principal more senior to this tranche. /// @param principalAmount Total principal in this tranche. /// @param lender Lender for this given tranche. /// @param accruedInterest Accrued Interest. /// @param startTime Start Time. Either the time at which the loan initiated / was refinanced. /// @param aprBps APR in basis points. struct Tranche { uint256 loanId; uint256 floor; uint256 principalAmount; address lender; uint256 accruedInterest; uint256 startTime; uint256 aprBps; } /// @dev Principal Amount is equal to the sum of all tranches principalAmount. /// We keep it for caching purposes. Since we are not saving this on chain but the hash, /// it does not have a huge impact on gas. /// @param borrower Borrower. /// @param nftCollateralTokenId NFT Collateral Token ID. /// @param nftCollateralAddress NFT Collateral Address. /// @param principalAddress Principal Address. /// @param principalAmount Principal Amount. /// @param startTime Start Time. /// @param duration Duration. /// @param tranche Tranches. /// @param protocolFee Protocol Fee. struct Loan { address borrower; uint256 nftCollateralTokenId; address nftCollateralAddress; address principalAddress; uint256 principalAmount; uint256 startTime; uint256 duration; Tranche[] tranche; uint256 protocolFee; } /// @notice Renegotiation offer. /// @param renegotiationId Renegotiation ID. /// @param loanId Loan ID. /// @param lender Lender. /// @param fee Fee. /// @param trancheIndex Tranche Indexes to be refinanced. /// @param principalAmount Principal Amount. If more than one tranche, it must be the sum. /// @param aprBps APR in basis points. /// @param expirationTime Expiration Time. /// @param duration Duration. struct RenegotiationOffer { uint256 renegotiationId; uint256 loanId; address lender; uint256 fee; uint256[] trancheIndex; uint256 principalAmount; uint256 aprBps; uint256 expirationTime; uint256 duration; } event LoanLiquidated(uint256 loanId); event LoanEmitted(uint256 loanId, uint256[] offerId, Loan loan, uint256 fee); event LoanRefinanced(uint256 renegotiationId, uint256 oldLoanId, uint256 newLoanId, Loan loan, uint256 fee); event LoanRepaid(uint256 loanId, uint256 totalRepayment, uint256 fee); event LoanRefinancedFromNewOffers( uint256 loanId, uint256 newLoanId, Loan loan, uint256[] offerIds, uint256 totalFee ); event Delegated(uint256 loanId, address delegate, bytes32 _rights, bool value); event FlashActionContractUpdated(address newFlashActionContract); event FlashActionExecuted(uint256 loanId, address target, bytes data); event RevokeDelegate(address delegate, address collection, uint256 tokenId, bytes32 _rights); event MinLockPeriodUpdated(uint256 minLockPeriod); /// @notice Call by the borrower when emiting a new loan. /// @param _loanExecutionData Loan execution data. /// @return loanId Loan ID. /// @return loan Loan. function emitLoan(LoanExecutionData calldata _loanExecutionData) external returns (uint256, Loan memory); /// @notice Refinance whole loan (leaving just one tranche). /// @param _renegotiationOffer Offer to refinance a loan. /// @param _loan Current loan. /// @param _renegotiationOfferSignature Signature of the offer. /// @return loanId New Loan Id, New Loan. function refinanceFull( RenegotiationOffer calldata _renegotiationOffer, Loan memory _loan, bytes calldata _renegotiationOfferSignature ) external returns (uint256, Loan memory); /// @notice Add a new tranche to a loan. /// @param _renegotiationOffer Offer for new tranche. /// @param _loan Current loan. /// @param _renegotiationOfferSignature Signature of the offer. /// @return loanId New Loan Id /// @return loan New Loan. function addNewTranche( RenegotiationOffer calldata _renegotiationOffer, Loan memory _loan, bytes calldata _renegotiationOfferSignature ) external returns (uint256, Loan memory); /// @notice Refinance a loan partially. It can only be called by the new lender /// (they are always a strict improvement on apr). /// @param _renegotiationOffer Offer to refinance a loan partially. /// @param _loan Current loan. /// @return loanId New Loan Id, New Loan. /// @return loan New Loan. function refinancePartial(RenegotiationOffer calldata _renegotiationOffer, Loan memory _loan) external returns (uint256, Loan memory); /// @notice Refinance a loan from LoanExecutionData. We let borrowers use outstanding offers for new loans /// to refinance their current loan. /// @param _loanId Loan ID. /// @param _loan Current loan. /// @param _loanExecutionData Loan Execution Data. /// @return loanId New Loan Id. /// @return loan New Loan. function refinanceFromLoanExecutionData( uint256 _loanId, Loan calldata _loan, LoanExecutionData calldata _loanExecutionData ) external returns (uint256, Loan memory); /// @notice Repay loan. Interest is calculated pro-rata based on time. Lender is defined by nft ownership. /// @param _repaymentData Repayment data. function repayLoan(LoanRepaymentData calldata _repaymentData) external; /// @notice Call when a loan is past its due date. /// @param _loanId Loan ID. /// @param _loan Loan. /// @return Liquidation Struct of the liquidation. function liquidateLoan(uint256 _loanId, Loan calldata _loan) external returns (bytes memory); /// @return getMaxTranches Maximum number of tranches per loan. function getMaxTranches() external view returns (uint256); /// @notice Set min lock period (in BPS). /// @param _minLockPeriod Min lock period. function setMinLockPeriod(uint256 _minLockPeriod) external; /// @notice Get min lock period (in BPS). /// @return minLockPeriod Min lock period. function getMinLockPeriod() external view returns (uint256); /// @notice Get delegation registry. /// @return delegateRegistry Delegate registry. function getDelegateRegistry() external view returns (address); /// @notice Delegate ownership. /// @param _loanId Loan ID. /// @param _loan Loan. /// @param _rights Delegation Rights. Empty for all. /// @param _delegate Delegate address. /// @param _value True if delegate, false if undelegate. function delegate(uint256 _loanId, Loan calldata _loan, address _delegate, bytes32 _rights, bool _value) external; /// @notice Anyone can reveke a delegation on an NFT that's no longer in escrow. /// @param _delegate Delegate address. /// @param _collection Collection address. /// @param _tokenId Token ID. /// @param _rights Delegation Rights. Empty for all. function revokeDelegate(address _delegate, address _collection, uint256 _tokenId, bytes32 _rights) external; /// @notice Get Flash Action Contract. /// @return flashActionContract Flash Action Contract. function getFlashActionContract() external view returns (address); /// @notice Update Flash Action Contract. /// @param _newFlashActionContract Flash Action Contract. function setFlashActionContract(address _newFlashActionContract) external; /// @notice Get Loan Hash. /// @param _loanId Loan ID. /// @return loanHash Loan Hash. function getLoanHash(uint256 _loanId) external view returns (bytes32); /// @notice Transfer NFT to the flash action contract (expected use cases here are for airdrops and similar scenarios). /// The flash action contract would implement specific interactions with given contracts. /// Only the the borrower can call this function for a given loan. By the end of the transaction, the NFT must have /// been returned to escrow. /// @param _loanId Loan ID. /// @param _loan Loan. /// @param _target Target address for the flash action contract to interact with. /// @param _data Data to be passed to be passed to the ultimate contract. function executeFlashAction(uint256 _loanId, Loan calldata _loan, address _target, bytes calldata _data) external; /// @notice Called by the liquidator for accounting purposes. /// @param _loanId The id of the loan. /// @param _loan The loan object. function loanLiquidated(uint256 _loanId, Loan calldata _loan) external; }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721.sol) pragma solidity ^0.8.20; import {IERC165} from "../../utils/introspection/IERC165.sol"; /** * @dev Required interface of an ERC-721 compliant contract. */ interface IERC721 is IERC165 { /** * @dev Emitted when `tokenId` token is transferred from `from` to `to`. */ event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); /** * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token. */ event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); /** * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. */ event ApprovalForAll(address indexed owner, address indexed operator, bool approved); /** * @dev Returns the number of tokens in ``owner``'s account. */ function balanceOf(address owner) external view returns (uint256 balance); /** * @dev Returns the owner of the `tokenId` token. * * Requirements: * * - `tokenId` must exist. */ function ownerOf(uint256 tokenId) external view returns (address owner); /** * @dev Safely transfers `tokenId` token from `from` to `to`. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must exist and be owned by `from`. * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon * a safe transfer. * * Emits a {Transfer} event. */ function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; /** * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients * are aware of the ERC-721 protocol to prevent tokens from being forever locked. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must exist and be owned by `from`. * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or * {setApprovalForAll}. * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon * a safe transfer. * * Emits a {Transfer} event. */ function safeTransferFrom(address from, address to, uint256 tokenId) external; /** * @dev Transfers `tokenId` token from `from` to `to`. * * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC-721 * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must * understand this adds an external call which potentially creates a reentrancy vulnerability. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must be owned by `from`. * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. * * Emits a {Transfer} event. */ function transferFrom(address from, address to, uint256 tokenId) external; /** * @dev Gives permission to `to` to transfer `tokenId` token to another account. * The approval is cleared when the token is transferred. * * Only a single account can be approved at a time, so approving the zero address clears previous approvals. * * Requirements: * * - The caller must own the token or be an approved operator. * - `tokenId` must exist. * * Emits an {Approval} event. */ function approve(address to, uint256 tokenId) external; /** * @dev Approve or remove `operator` as an operator for the caller. * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller. * * Requirements: * * - The `operator` cannot be the address zero. * * Emits an {ApprovalForAll} event. */ function setApprovalForAll(address operator, bool approved) external; /** * @dev Returns the account approved for `tokenId` token. * * Requirements: * * - `tokenId` must exist. */ function getApproved(uint256 tokenId) external view returns (address operator); /** * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`. * * See {setApprovalForAll} */ function isApprovedForAll(address owner, address operator) external view returns (bool); }
// SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.21; import "@solmate/auth/Owned.sol"; /// @title TwoStepOwned /// @author Florida St /// @notice This contract is used to transfer ownership of a contract in two steps. abstract contract TwoStepOwned is Owned { event TransferOwnerRequested(address newOwner); error TooSoonError(); error InvalidInputError(); uint256 public immutable MIN_WAIT_TIME; address public pendingOwner; uint256 public pendingOwnerTime; constructor(address _owner, uint256 _minWaitTime) Owned(_owner) { pendingOwnerTime = type(uint256).max; MIN_WAIT_TIME = _minWaitTime; } /// @notice First step transferring ownership to the new owner. /// @param _newOwner The address of the new owner. function requestTransferOwner(address _newOwner) external onlyOwner { pendingOwner = _newOwner; pendingOwnerTime = block.timestamp; emit TransferOwnerRequested(_newOwner); } /// @notice Second step transferring ownership to the new owner. function transferOwnership() public { address newOwner = msg.sender; if (pendingOwnerTime + MIN_WAIT_TIME > block.timestamp) { revert TooSoonError(); } if (newOwner != pendingOwner) { revert InvalidInputError(); } owner = newOwner; pendingOwner = address(0); pendingOwnerTime = type(uint256).max; emit OwnershipTransferred(owner, newOwner); } }
// SPDX-License-Identifier: CC0-1.0 pragma solidity >=0.8.13; /** * @title IDelegateRegistry * @custom:version 2.0 * @custom:author foobar (0xfoobar) * @notice A standalone immutable registry storing delegated permissions from one address to another */ interface IDelegateRegistry { /// @notice Delegation type, NONE is used when a delegation does not exist or is revoked enum DelegationType { NONE, ALL, CONTRACT, ERC721, ERC20, ERC1155 } /// @notice Struct for returning delegations struct Delegation { DelegationType type_; address to; address from; bytes32 rights; address contract_; uint256 tokenId; uint256 amount; } /// @notice Emitted when an address delegates or revokes rights for their entire wallet event DelegateAll(address indexed from, address indexed to, bytes32 rights, bool enable); /// @notice Emitted when an address delegates or revokes rights for a contract address event DelegateContract(address indexed from, address indexed to, address indexed contract_, bytes32 rights, bool enable); /// @notice Emitted when an address delegates or revokes rights for an ERC721 tokenId event DelegateERC721(address indexed from, address indexed to, address indexed contract_, uint256 tokenId, bytes32 rights, bool enable); /// @notice Emitted when an address delegates or revokes rights for an amount of ERC20 tokens event DelegateERC20(address indexed from, address indexed to, address indexed contract_, bytes32 rights, uint256 amount); /// @notice Emitted when an address delegates or revokes rights for an amount of an ERC1155 tokenId event DelegateERC1155(address indexed from, address indexed to, address indexed contract_, uint256 tokenId, bytes32 rights, uint256 amount); /// @notice Thrown if multicall calldata is malformed error MulticallFailed(); /** * ----------- WRITE ----------- */ /** * @notice Call multiple functions in the current contract and return the data from all of them if they all succeed * @param data The encoded function data for each of the calls to make to this contract * @return results The results from each of the calls passed in via data */ function multicall(bytes[] calldata data) external payable returns (bytes[] memory results); /** * @notice Allow the delegate to act on behalf of `msg.sender` for all contracts * @param to The address to act as delegate * @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights * @param enable Whether to enable or disable this delegation, true delegates and false revokes * @return delegationHash The unique identifier of the delegation */ function delegateAll(address to, bytes32 rights, bool enable) external payable returns (bytes32 delegationHash); /** * @notice Allow the delegate to act on behalf of `msg.sender` for a specific contract * @param to The address to act as delegate * @param contract_ The contract whose rights are being delegated * @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights * @param enable Whether to enable or disable this delegation, true delegates and false revokes * @return delegationHash The unique identifier of the delegation */ function delegateContract(address to, address contract_, bytes32 rights, bool enable) external payable returns (bytes32 delegationHash); /** * @notice Allow the delegate to act on behalf of `msg.sender` for a specific ERC721 token * @param to The address to act as delegate * @param contract_ The contract whose rights are being delegated * @param tokenId The token id to delegate * @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights * @param enable Whether to enable or disable this delegation, true delegates and false revokes * @return delegationHash The unique identifier of the delegation */ function delegateERC721(address to, address contract_, uint256 tokenId, bytes32 rights, bool enable) external payable returns (bytes32 delegationHash); /** * @notice Allow the delegate to act on behalf of `msg.sender` for a specific amount of ERC20 tokens * @dev The actual amount is not encoded in the hash, just the existence of a amount (since it is an upper bound) * @param to The address to act as delegate * @param contract_ The address for the fungible token contract * @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights * @param amount The amount to delegate, > 0 delegates and 0 revokes * @return delegationHash The unique identifier of the delegation */ function delegateERC20(address to, address contract_, bytes32 rights, uint256 amount) external payable returns (bytes32 delegationHash); /** * @notice Allow the delegate to act on behalf of `msg.sender` for a specific amount of ERC1155 tokens * @dev The actual amount is not encoded in the hash, just the existence of a amount (since it is an upper bound) * @param to The address to act as delegate * @param contract_ The address of the contract that holds the token * @param tokenId The token id to delegate * @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights * @param amount The amount of that token id to delegate, > 0 delegates and 0 revokes * @return delegationHash The unique identifier of the delegation */ function delegateERC1155(address to, address contract_, uint256 tokenId, bytes32 rights, uint256 amount) external payable returns (bytes32 delegationHash); /** * ----------- CHECKS ----------- */ /** * @notice Check if `to` is a delegate of `from` for the entire wallet * @param to The potential delegate address * @param from The potential address who delegated rights * @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only * @return valid Whether delegate is granted to act on the from's behalf */ function checkDelegateForAll(address to, address from, bytes32 rights) external view returns (bool); /** * @notice Check if `to` is a delegate of `from` for the specified `contract_` or the entire wallet * @param to The delegated address to check * @param contract_ The specific contract address being checked * @param from The cold wallet who issued the delegation * @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only * @return valid Whether delegate is granted to act on from's behalf for entire wallet or that specific contract */ function checkDelegateForContract(address to, address from, address contract_, bytes32 rights) external view returns (bool); /** * @notice Check if `to` is a delegate of `from` for the specific `contract` and `tokenId`, the entire `contract_`, or the entire wallet * @param to The delegated address to check * @param contract_ The specific contract address being checked * @param tokenId The token id for the token to delegating * @param from The wallet that issued the delegation * @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only * @return valid Whether delegate is granted to act on from's behalf for entire wallet, that contract, or that specific tokenId */ function checkDelegateForERC721(address to, address from, address contract_, uint256 tokenId, bytes32 rights) external view returns (bool); /** * @notice Returns the amount of ERC20 tokens the delegate is granted rights to act on the behalf of * @param to The delegated address to check * @param contract_ The address of the token contract * @param from The cold wallet who issued the delegation * @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only * @return balance The delegated balance, which will be 0 if the delegation does not exist */ function checkDelegateForERC20(address to, address from, address contract_, bytes32 rights) external view returns (uint256); /** * @notice Returns the amount of a ERC1155 tokens the delegate is granted rights to act on the behalf of * @param to The delegated address to check * @param contract_ The address of the token contract * @param tokenId The token id to check the delegated amount of * @param from The cold wallet who issued the delegation * @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only * @return balance The delegated balance, which will be 0 if the delegation does not exist */ function checkDelegateForERC1155(address to, address from, address contract_, uint256 tokenId, bytes32 rights) external view returns (uint256); /** * ----------- ENUMERATIONS ----------- */ /** * @notice Returns all enabled delegations a given delegate has received * @param to The address to retrieve delegations for * @return delegations Array of Delegation structs */ function getIncomingDelegations(address to) external view returns (Delegation[] memory delegations); /** * @notice Returns all enabled delegations an address has given out * @param from The address to retrieve delegations for * @return delegations Array of Delegation structs */ function getOutgoingDelegations(address from) external view returns (Delegation[] memory delegations); /** * @notice Returns all hashes associated with enabled delegations an address has received * @param to The address to retrieve incoming delegation hashes for * @return delegationHashes Array of delegation hashes */ function getIncomingDelegationHashes(address to) external view returns (bytes32[] memory delegationHashes); /** * @notice Returns all hashes associated with enabled delegations an address has given out * @param from The address to retrieve outgoing delegation hashes for * @return delegationHashes Array of delegation hashes */ function getOutgoingDelegationHashes(address from) external view returns (bytes32[] memory delegationHashes); /** * @notice Returns the delegations for a given array of delegation hashes * @param delegationHashes is an array of hashes that correspond to delegations * @return delegations Array of Delegation structs, return empty structs for nonexistent or revoked delegations */ function getDelegationsFromHashes(bytes32[] calldata delegationHashes) external view returns (Delegation[] memory delegations); /** * ----------- STORAGE ACCESS ----------- */ /** * @notice Allows external contracts to read arbitrary storage slots */ function readSlot(bytes32 location) external view returns (bytes32); /** * @notice Allows external contracts to read an arbitrary array of storage slots */ function readSlots(bytes32[] calldata locations) external view returns (bytes32[] memory); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/ECDSA.sol) pragma solidity ^0.8.20; /** * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations. * * These functions can be used to verify that a message was signed by the holder * of the private keys of a given address. */ library ECDSA { enum RecoverError { NoError, InvalidSignature, InvalidSignatureLength, InvalidSignatureS } /** * @dev The signature derives the `address(0)`. */ error ECDSAInvalidSignature(); /** * @dev The signature has an invalid length. */ error ECDSAInvalidSignatureLength(uint256 length); /** * @dev The signature has an S value that is in the upper half order. */ error ECDSAInvalidSignatureS(bytes32 s); /** * @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not * return address(0) without also returning an error description. Errors are documented using an enum (error type) * and a bytes32 providing additional information about the error. * * If no error is returned, then the address can be used for verification purposes. * * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures: * this function rejects them by requiring the `s` value to be in the lower * half order, and the `v` value to be either 27 or 28. * * IMPORTANT: `hash` _must_ be the result of a hash operation for the * verification to be secure: it is possible to craft signatures that * recover to arbitrary addresses for non-hashed data. A safe way to ensure * this is by receiving a hash of the original message (which may otherwise * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it. * * Documentation for signature generation: * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js] * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers] */ function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError, bytes32) { if (signature.length == 65) { bytes32 r; bytes32 s; uint8 v; // ecrecover takes the signature parameters, and the only way to get them // currently is to use assembly. /// @solidity memory-safe-assembly assembly { r := mload(add(signature, 0x20)) s := mload(add(signature, 0x40)) v := byte(0, mload(add(signature, 0x60))) } return tryRecover(hash, v, r, s); } else { return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length)); } } /** * @dev Returns the address that signed a hashed message (`hash`) with * `signature`. This address can then be used for verification purposes. * * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures: * this function rejects them by requiring the `s` value to be in the lower * half order, and the `v` value to be either 27 or 28. * * IMPORTANT: `hash` _must_ be the result of a hash operation for the * verification to be secure: it is possible to craft signatures that * recover to arbitrary addresses for non-hashed data. A safe way to ensure * this is by receiving a hash of the original message (which may otherwise * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it. */ function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature); _throwError(error, errorArg); return recovered; } /** * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately. * * See https://eips.ethereum.org/EIPS/eip-2098[ERC-2098 short signatures] */ function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError, bytes32) { unchecked { bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); // We do not check for an overflow here since the shift operation results in 0 or 1. uint8 v = uint8((uint256(vs) >> 255) + 27); return tryRecover(hash, v, r, s); } } /** * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately. */ function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) { (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs); _throwError(error, errorArg); return recovered; } /** * @dev Overload of {ECDSA-tryRecover} that receives the `v`, * `r` and `s` signature fields separately. */ function tryRecover( bytes32 hash, uint8 v, bytes32 r, bytes32 s ) internal pure returns (address, RecoverError, bytes32) { // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines // the valid range for s in (301): 0 < s < secp256k1n √∑ 2 + 1, and for v in (302): v ‚àà {27, 28}. Most // signatures from current libraries generate a unique signature with an s-value in the lower half order. // // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept // these malleable signatures as well. if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { return (address(0), RecoverError.InvalidSignatureS, s); } // If the signature is valid (and not malleable), return the signer address address signer = ecrecover(hash, v, r, s); if (signer == address(0)) { return (address(0), RecoverError.InvalidSignature, bytes32(0)); } return (signer, RecoverError.NoError, bytes32(0)); } /** * @dev Overload of {ECDSA-recover} that receives the `v`, * `r` and `s` signature fields separately. */ function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) { (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s); _throwError(error, errorArg); return recovered; } /** * @dev Optionally reverts with the corresponding custom error according to the `error` argument provided. */ function _throwError(RecoverError error, bytes32 errorArg) private pure { if (error == RecoverError.NoError) { return; // no error: do nothing } else if (error == RecoverError.InvalidSignature) { revert ECDSAInvalidSignature(); } else if (error == RecoverError.InvalidSignatureLength) { revert ECDSAInvalidSignatureLength(uint256(errorArg)); } else if (error == RecoverError.InvalidSignatureS) { revert ECDSAInvalidSignatureS(errorArg); } } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; /// @notice Gas optimized reentrancy protection for smart contracts. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ReentrancyGuard.sol) /// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol) abstract contract ReentrancyGuard { uint256 private locked = 1; modifier nonReentrant() virtual { require(locked == 1, "REENTRANCY"); locked = 2; _; locked = 1; } }
// SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.20; import "../loans/IMultiSourceLoan.sol"; /// @title Interface for Loan Offer Validators. /// @author Florida St /// @notice Verify the given `_offer` is valid for `_tokenId` and `_validatorData`. interface IOfferValidator { /// @notice Validate a loan offer. function validateOffer(IMultiSourceLoan.LoanOffer calldata _offer, uint256 _tokenId, bytes calldata _validatorData) external view; }
// SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.20; /// @title NFT Flash Action Interface /// @author Florida St /// @notice Interface for Flash Actions on NFTs in outstanding loans. interface INFTFlashAction { error InvalidOwnerError(); /// @notice Execute an arbitrary flash action on a given NFT. This contract owns it and must return it. /// @param _collection The NFT collection. /// @param _tokenId The NFT token ID. /// @param _target The target contract. /// @param _data The data to send to the target. function execute(address _collection, uint256 _tokenId, address _target, bytes calldata _data) external; }
// SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.21; /// @title Multi Source Loan Interface /// @author Florida St /// @notice A multi source loan is one with multiple tranches. interface ILoanManager { struct ProposedCaller { address caller; bool isLoanContract; } /// @notice Validate an offer. Can only be called by an accepted caller. /// @param tokenId The token id. /// @param offer The offer to validate. /// @param protocolFee The protocol fee. function validateOffer(uint256 tokenId, bytes calldata offer, uint256 protocolFee) external; /// @notice Add allowed callers. /// @param caller The callers to add. function addCaller(ProposedCaller calldata caller) external; /// @notice Called on loan repayment. /// @param loanId The loan id. /// @param principalAmount The principal amount. /// @param apr The APR. /// @param accruedInterest The accrued interest. /// @param protocolFee The protocol fee. /// @param startTime The start time. function loanRepayment( uint256 loanId, uint256 principalAmount, uint256 apr, uint256 accruedInterest, uint256 protocolFee, uint256 startTime ) external; /// @notice Called on loan liquidation. /// @param loanAddress The address of the loan contract since this might be called by a liquidator. /// @param loanId The loan id. /// @param principalAmount The principal amount. /// @param apr The APR. /// @param accruedInterest The accrued interest. /// @param protocolFee The protocol fee. /// @param received The received amount (from liquidation proceeds) /// @param startTime The start time. function loanLiquidation( address loanAddress, uint256 loanId, uint256 principalAmount, uint256 apr, uint256 accruedInterest, uint256 protocolFee, uint256 received, uint256 startTime ) external; }
// SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.20; /// @title Interface Loan Manager Registry /// @author Florida St /// @notice Interface for a Loan Manager Registry. interface ILoanManagerRegistry { /// @notice Add a loan manager to the registry /// @param _loanManager Address of the loan manager function addLoanManager(address _loanManager) external; /// @notice Remove a loan manager from the registry /// @param _loanManager Address of the loan manager function removeLoanManager(address _loanManager) external; /// @notice Check if a loan manager is registered /// @param _loanManager Address of the loan manager /// @return True if the loan manager is registered function isLoanManager(address _loanManager) external view returns (bool); }
// SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.21; import "../../interfaces/loans/IMultiSourceLoan.sol"; import "../../interfaces/loans/IBaseLoan.sol"; import "../../interfaces/IAuctionLoanLiquidator.sol"; import {ITradeMarketplace} from "../../interfaces/callbacks/ITradeMarketplace.sol"; library Hash { bytes32 private constant _VALIDATOR_HASH = keccak256("OfferValidator(address validator,bytes arguments)"); bytes32 private constant _LOAN_OFFER_HASH = keccak256( "LoanOffer(uint256 offerId,address lender,uint256 fee,uint256 capacity,address nftCollateralAddress,uint256 nftCollateralTokenId,address principalAddress,uint256 principalAmount,uint256 aprBps,uint256 expirationTime,uint256 duration,uint256 maxSeniorRepayment,OfferValidator[] validators)OfferValidator(address validator,bytes arguments)" ); bytes32 private constant _OFFER_EXECUTION_HASH = keccak256( "OfferExecution(LoanOffer offer,uint256 amount,bytes lenderOfferSignature)LoanOffer(uint256 offerId,address lender,uint256 fee,uint256 capacity,address nftCollateralAddress,uint256 nftCollateralTokenId,address principalAddress,uint256 principalAmount,uint256 aprBps,uint256 expirationTime,uint256 duration,uint256 maxSeniorRepayment,OfferValidator[] validators)OfferValidator(address validator,bytes arguments)" ); bytes32 private constant _EXECUTION_DATA_HASH = keccak256( "ExecutionData(OfferExecution[] offerExecution,uint256 tokenId,uint256 duration,uint256 expirationTime,address principalReceiver,bytes callbackData)LoanOffer(uint256 offerId,address lender,uint256 fee,uint256 capacity,address nftCollateralAddress,uint256 nftCollateralTokenId,address principalAddress,uint256 principalAmount,uint256 aprBps,uint256 expirationTime,uint256 duration,uint256 maxSeniorRepayment,OfferValidator[] validators)OfferExecution(LoanOffer offer,uint256 amount,bytes lenderOfferSignature)OfferValidator(address validator,bytes arguments)" ); bytes32 private constant _SIGNABLE_REPAYMENT_DATA_HASH = keccak256("SignableRepaymentData(uint256 loanId,bytes callbackData,bool shouldDelegate)"); bytes32 private constant _MULTI_SOURCE_LOAN_HASH = keccak256( "Loan(address borrower,uint256 nftCollateralTokenId,address nftCollateralAddress,address principalAddress,uint256 principalAmount,uint256 startTime,uint256 duration,Tranche[] tranche,uint256 protocolFee)Source(uint256 loanId,address lender,uint256 principalAmount,uint256 accruedInterest,uint256 startTime,uint256 aprBps)Tranche(uint256 floor,uint256 principalAmount,Source[] source)" ); bytes32 private constant _TRANCHE_HASH = keccak256( "Tranche(uint256 loanId,uint256 floor,uint256 principalAmount,address lender,uint256 accruedInterest,uint256 startTime,uint256 aprBps)" ); bytes32 private constant _MULTI_RENEGOTIATION_OFFER_HASH = keccak256( "RenegotiationOffer(uint256 renegotiationId,uint256 loanId,address lender,uint256 fee,uint256[] trancheIndex,uint256 principalAmount,uint256 aprBps,uint256 expirationTime,uint256 duration)" ); bytes32 private constant _AUCTION_HASH = keccak256( "Auction(address loanAddress,uint256 loanId,uint256 highestBid,uint256 triggerFee,uint256 minBid,address highestBidder,uint96 duration,address asset,uint96 startTime,address originator,uint96 lastBidTime)" ); bytes32 private constant _TRADE_ORDER_HASH = keccak256( "Order(address maker,address taker,address collection,uint256 tokenId,address currency,uint256 price,uint256 nonce,uint256 expiration,bool isAsk)" ); function hash(IMultiSourceLoan.LoanOffer memory _loanOffer) internal pure returns (bytes32) { bytes memory encodedValidators; uint256 totalValidators = _loanOffer.validators.length; for (uint256 i = 0; i < totalValidators;) { encodedValidators = abi.encodePacked(encodedValidators, _hashValidator(_loanOffer.validators[i])); unchecked { ++i; } } return keccak256( abi.encode( _LOAN_OFFER_HASH, _loanOffer.offerId, _loanOffer.lender, _loanOffer.fee, _loanOffer.capacity, _loanOffer.nftCollateralAddress, _loanOffer.nftCollateralTokenId, _loanOffer.principalAddress, _loanOffer.principalAmount, _loanOffer.aprBps, _loanOffer.expirationTime, _loanOffer.duration, _loanOffer.maxSeniorRepayment, keccak256(encodedValidators) ) ); } function hash(IMultiSourceLoan.ExecutionData memory _executionData) internal pure returns (bytes32) { bytes memory encodedOfferExecution; uint256 totalOfferExecution = _executionData.offerExecution.length; for (uint256 i = 0; i < totalOfferExecution;) { encodedOfferExecution = abi.encodePacked(encodedOfferExecution, _hashOfferExecution(_executionData.offerExecution[i])); unchecked { ++i; } } return keccak256( abi.encode( _EXECUTION_DATA_HASH, keccak256(encodedOfferExecution), _executionData.tokenId, _executionData.duration, _executionData.expirationTime, _executionData.principalReceiver, keccak256(_executionData.callbackData) ) ); } function hash(IMultiSourceLoan.SignableRepaymentData memory _repaymentData) internal pure returns (bytes32) { return keccak256( abi.encode( _SIGNABLE_REPAYMENT_DATA_HASH, _repaymentData.loanId, keccak256(_repaymentData.callbackData), _repaymentData.shouldDelegate ) ); } function hash(IMultiSourceLoan.Loan memory _loan) internal pure returns (bytes32) { bytes memory trancheHashes; uint256 totalTranches = _loan.tranche.length; for (uint256 i; i < totalTranches;) { trancheHashes = abi.encodePacked(trancheHashes, _hashTranche(_loan.tranche[i])); unchecked { ++i; } } return keccak256( abi.encode( _MULTI_SOURCE_LOAN_HASH, _loan.borrower, _loan.nftCollateralTokenId, _loan.nftCollateralAddress, _loan.principalAddress, _loan.principalAmount, _loan.startTime, _loan.duration, keccak256(trancheHashes), _loan.protocolFee ) ); } function hash(IMultiSourceLoan.RenegotiationOffer memory _refinanceOffer) internal pure returns (bytes32) { bytes memory encodedIndexes; uint256 totalIndexes = _refinanceOffer.trancheIndex.length; for (uint256 i = 0; i < totalIndexes;) { encodedIndexes = abi.encodePacked(encodedIndexes, _refinanceOffer.trancheIndex[i]); unchecked { ++i; } } return keccak256( abi.encode( _MULTI_RENEGOTIATION_OFFER_HASH, _refinanceOffer.renegotiationId, _refinanceOffer.loanId, _refinanceOffer.lender, _refinanceOffer.fee, keccak256(encodedIndexes), _refinanceOffer.principalAmount, _refinanceOffer.aprBps, _refinanceOffer.expirationTime, _refinanceOffer.duration ) ); } function hash(IAuctionLoanLiquidator.Auction memory _auction) internal pure returns (bytes32) { return keccak256( abi.encode( _AUCTION_HASH, _auction.loanAddress, _auction.loanId, _auction.highestBid, _auction.triggerFee, _auction.minBid, _auction.highestBidder, _auction.duration, _auction.asset, _auction.startTime, _auction.originator, _auction.lastBidTime ) ); } function hash(ITradeMarketplace.Order memory order) internal pure returns (bytes32) { return keccak256( abi.encode( _TRADE_ORDER_HASH, order.maker, order.taker, order.collection, order.tokenId, order.currency, order.price, order.nonce, order.expiration, order.isAsk ) ); } function _hashTranche(IMultiSourceLoan.Tranche memory _tranche) private pure returns (bytes32) { return keccak256( abi.encode( _TRANCHE_HASH, _tranche.loanId, _tranche.floor, _tranche.principalAmount, _tranche.lender, _tranche.accruedInterest, _tranche.startTime, _tranche.aprBps ) ); } function _hashValidator(IBaseLoan.OfferValidator memory _validator) private pure returns (bytes32) { return keccak256(abi.encode(_VALIDATOR_HASH, _validator.validator, keccak256(_validator.arguments))); } function _hashOfferExecution(IMultiSourceLoan.OfferExecution memory _offerExecution) private pure returns (bytes32) { return keccak256( abi.encode( _OFFER_EXECUTION_HASH, hash(_offerExecution.offer), _offerExecution.amount, keccak256(_offerExecution.lenderOfferSignature) ) ); } }
// SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.21; import "@solmate/utils/FixedPointMathLib.sol"; import "../../interfaces/loans/IMultiSourceLoan.sol"; import "../../interfaces/loans/IBaseLoan.sol"; library Interest { using FixedPointMathLib for uint256; uint256 private constant _PRECISION = 10000; uint256 private constant _SECONDS_PER_YEAR = 31536000; function getInterest(IMultiSourceLoan.LoanOffer memory _loanOffer) internal pure returns (uint256) { return _getInterest(_loanOffer.principalAmount, _loanOffer.aprBps, _loanOffer.duration); } function getInterest(uint256 _amount, uint256 _aprBps, uint256 _duration) internal pure returns (uint256) { return _getInterest(_amount, _aprBps, _duration); } function getTotalOwed(IMultiSourceLoan.Loan memory _loan, uint256 _timestamp) internal pure returns (uint256) { uint256 owed = 0; for (uint256 i = 0; i < _loan.tranche.length;) { IMultiSourceLoan.Tranche memory tranche = _loan.tranche[i]; owed += tranche.principalAmount + tranche.accruedInterest + _getInterest(tranche.principalAmount, tranche.aprBps, _timestamp - tranche.startTime); unchecked { ++i; } } return owed; } function _getInterest(uint256 _amount, uint256 _aprBps, uint256 _duration) private pure returns (uint256) { return _amount.mulDivUp(_aprBps * _duration, _PRECISION * _SECONDS_PER_YEAR); } }
// SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.21; import "../interfaces/IMulticall.sol"; /// @title Multicall /// @author Florida St /// @notice Base implementation for multicall. abstract contract Multicall is IMulticall { function multicall(bytes[] calldata data) external payable override returns (bytes[] memory results) { results = new bytes[](data.length); bool success; uint256 totalCalls = data.length; for (uint256 i = 0; i < totalCalls;) { //slither-disable-next-line calls-loop,delegatecall-loop (success, results[i]) = address(this).delegatecall(data[i]); if (!success) { revert MulticallFailed(i, results[i]); } unchecked { ++i; } } } }
// SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.21; import "@openzeppelin/utils/cryptography/MessageHashUtils.sol"; import "@openzeppelin/interfaces/IERC1271.sol"; import "@solmate/auth/Owned.sol"; import "@solmate/tokens/ERC721.sol"; import "@solmate/utils/FixedPointMathLib.sol"; import "../../interfaces/loans/IBaseLoan.sol"; import "../utils/Hash.sol"; import "../AddressManager.sol"; import "../LiquidationHandler.sol"; /// @title BaseLoan /// @author Florida St /// @notice Base implementation that we expect all loans to share. Offers can either be /// for new loans or renegotiating existing ones. /// Offers are signed off-chain. /// Offers have a nonce associated that is used for cancelling and /// marking as executed. abstract contract BaseLoan is ERC721TokenReceiver, IBaseLoan, LiquidationHandler { using FixedPointMathLib for uint256; using InputChecker for address; using MessageHashUtils for bytes32; /// @notice Used in compliance with EIP712 uint256 internal immutable INITIAL_CHAIN_ID; bytes32 public immutable INITIAL_DOMAIN_SEPARATOR; bytes4 internal constant MAGICVALUE_1271 = 0x1626ba7e; /// @notice Precision used for calculating interests. uint256 internal constant _PRECISION = 10000; bytes public constant VERSION = '3.1'; /// @notice Minimum improvement (in BPS) required for a strict improvement. uint256 internal _minImprovementApr = 1000; string public name; /// @notice Total number of loans issued. Given it's a serial value, we use it /// as loan id. uint256 public override getTotalLoansIssued; /// @notice Offer capacity mapping(address user => mapping(uint256 offerId => uint256 used)) internal _used; /// @notice Used for validate off chain maker offers / canceling one mapping(address user => mapping(uint256 offerId => bool notActive)) public isOfferCancelled; /// @notice Used for validating off chain maker offers / canceling all mapping(address user => uint256 minOfferId) public minOfferId; /// @notice Used in a similar way as `isOfferCancelled` to handle renegotiations. mapping(address user => mapping(uint256 renegotiationIf => bool notActive)) public isRenegotiationOfferCancelled; /// @notice Loans are only denominated in whitelisted addresses. Within each struct, /// we save those as their `uint` representation. AddressManager internal immutable _currencyManager; /// @notice Only whilteslited collections are accepted as collateral. Within each struct, /// we save those as their `uint` representation. AddressManager internal immutable _collectionManager; event OfferCancelled(address lender, uint256 offerId); event AllOffersCancelled(address lender, uint256 minOfferId); event RenegotiationOfferCancelled(address lender, uint256 renegotiationId); event MinAprImprovementUpdated(uint256 _minimum); error CancelledOrExecutedOfferError(address _lender, uint256 _offerId); error ExpiredOfferError(uint256 _expirationTime); error LowOfferIdError(address _lender, uint256 _newMinOfferId, uint256 _minOfferId); error LowRenegotiationOfferIdError(address _lender, uint256 _newMinRenegotiationOfferId, uint256 _minOfferId); error ZeroInterestError(); error InvalidSignatureError(); error CurrencyNotWhitelistedError(); error CollectionNotWhitelistedError(); error MaxCapacityExceededError(); error InvalidLoanError(uint256 _loanId); error NotStrictlyImprovedError(); error InvalidAmountError(uint256 _amount, uint256 _principalAmount); /// @notice Constructor /// @param _name The name of the loan contract /// @param currencyManager The address of the currency manager /// @param collectionManager The address of the collection manager /// @param protocolFee The protocol fee /// @param loanLiquidator The liquidator contract /// @param owner The owner of the contract /// @param minWaitTime The time to wait before a new owner can be set constructor( string memory _name, address currencyManager, address collectionManager, ProtocolFee memory protocolFee, address loanLiquidator, address owner, uint256 minWaitTime ) LiquidationHandler(owner, minWaitTime, loanLiquidator, protocolFee) { name = _name; currencyManager.checkNotZero(); collectionManager.checkNotZero(); _currencyManager = AddressManager(currencyManager); _collectionManager = AddressManager(collectionManager); INITIAL_CHAIN_ID = block.chainid; INITIAL_DOMAIN_SEPARATOR = _computeDomainSeparator(); } /// @return The minimum improvement for a loan to be considered strictly better. function getMinImprovementApr() external view returns (uint256) { return _minImprovementApr; } /// @notice Updates the minimum improvement for a loan to be considered strictly better. /// Only the owner can call this function. /// @param _newMinimum The new minimum improvement. function updateMinImprovementApr(uint256 _newMinimum) external onlyOwner { _minImprovementApr = _newMinimum; emit MinAprImprovementUpdated(_minImprovementApr); } /// @return Address of the currency manager. function getCurrencyManager() external view returns (address) { return address(_currencyManager); } /// @return Address of the collection manager. function getCollectionManager() external view returns (address) { return address(_collectionManager); } /// @inheritdoc IBaseLoan function cancelOffer(uint256 _offerId) external { address user = msg.sender; isOfferCancelled[user][_offerId] = true; emit OfferCancelled(user, _offerId); } /// @inheritdoc IBaseLoan function cancelAllOffers(uint256 _minOfferId) external virtual { address user = msg.sender; uint256 currentMinOfferId = minOfferId[user]; if (currentMinOfferId >= _minOfferId) { revert LowOfferIdError(user, _minOfferId, currentMinOfferId); } minOfferId[user] = _minOfferId; emit AllOffersCancelled(user, _minOfferId); } /// @inheritdoc IBaseLoan function cancelRenegotiationOffer(uint256 _renegotiationId) external virtual { address lender = msg.sender; isRenegotiationOfferCancelled[lender][_renegotiationId] = true; emit RenegotiationOfferCancelled(lender, _renegotiationId); } /// @notice Returns the remaining capacity for a given loan offer. /// @param _lender The address of the lender. /// @param _offerId The id of the offer. /// @return The amount lent out. function getUsedCapacity(address _lender, uint256 _offerId) external view returns (uint256) { return _used[_lender][_offerId]; } /// @notice Get the domain separator requried to comply with EIP-712. function DOMAIN_SEPARATOR() public view returns (bytes32) { return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : _computeDomainSeparator(); } /// @notice Call when issuing a new loan to get/set a unique serial id. /// @dev This id should never be 0. /// @return The new loan id. function _getAndSetNewLoanId() internal returns (uint256) { unchecked { return ++getTotalLoansIssued; } } /// @notice Compute domain separator for EIP-712. /// @return The domain separator. function _computeDomainSeparator() private view returns (bytes32) { return keccak256( abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak256(bytes(name)), keccak256(VERSION), block.chainid, address(this) ) ); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/MessageHashUtils.sol) pragma solidity ^0.8.20; import {Strings} from "../Strings.sol"; /** * @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing. * * The library provides methods for generating a hash of a message that conforms to the * https://eips.ethereum.org/EIPS/eip-191[ERC-191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712] * specifications. */ library MessageHashUtils { /** * @dev Returns the keccak256 digest of an ERC-191 signed data with version * `0x45` (`personal_sign` messages). * * The digest is calculated by prefixing a bytes32 `messageHash` with * `"\x19Ethereum Signed Message:\n32"` and hashing the result. It corresponds with the * hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method. * * NOTE: The `messageHash` parameter is intended to be the result of hashing a raw message with * keccak256, although any bytes32 value can be safely used because the final digest will * be re-hashed. * * See {ECDSA-recover}. */ function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) { /// @solidity memory-safe-assembly assembly { mstore(0x00, "\x19Ethereum Signed Message:\n32") // 32 is the bytes-length of messageHash mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20) } } /** * @dev Returns the keccak256 digest of an ERC-191 signed data with version * `0x45` (`personal_sign` messages). * * The digest is calculated by prefixing an arbitrary `message` with * `"\x19Ethereum Signed Message:\n" + len(message)` and hashing the result. It corresponds with the * hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method. * * See {ECDSA-recover}. */ function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32) { return keccak256(bytes.concat("\x19Ethereum Signed Message:\n", bytes(Strings.toString(message.length)), message)); } /** * @dev Returns the keccak256 digest of an ERC-191 signed data with version * `0x00` (data with intended validator). * * The digest is calculated by prefixing an arbitrary `data` with `"\x19\x00"` and the intended * `validator` address. Then hashing the result. * * See {ECDSA-recover}. */ function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) { return keccak256(abi.encodePacked(hex"19_00", validator, data)); } /** * @dev Returns the keccak256 digest of an EIP-712 typed data (ERC-191 version `0x01`). * * The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with * `\x19\x01` and hashing the result. It corresponds to the hash signed by the * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712. * * See {ECDSA-recover}. */ function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) { /// @solidity memory-safe-assembly assembly { let ptr := mload(0x40) mstore(ptr, hex"19_01") mstore(add(ptr, 0x02), domainSeparator) mstore(add(ptr, 0x22), structHash) digest := keccak256(ptr, 0x42) } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import "@openzeppelin/utils/cryptography/ECDSA.sol"; interface ITradeMarketplace { struct Order { address maker; address taker; address collection; uint256 tokenId; address currency; uint256 price; uint256 nonce; uint256 expiration; bool isAsk; bytes signature; } event OrderExecuted( address indexed maker, address indexed taker, address indexed collection, uint256 tokenId, address currency, uint256 price, uint256 nonce, uint256 expiration, bool isAsk ); event AllOrdersCancelled(address indexed user, uint256 nonce); error OrderExpired(); error OrderCancelled(); error InvalidTaker(); error LowNonceError(address user, uint256 nonce, uint256 currentMinNonce); error InvalidSignature(); function executeOrder(Order memory order) external; function cancelOrder(uint256 nonce) external; function cancelAllOrders(uint256 nonce) external; function isOrderCancelled(Order memory order) external view returns (bool); function orderHash(Order memory order) external view returns (bytes32); }
// SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.20; import "../../interfaces/ILoanLiquidator.sol"; /// @title Interface for Loans. /// @author Florida St /// @notice Basic Loan interface IBaseLoan { /// @notice Minimum improvement (in BPS) required for a strict improvement. /// @param principalAmount Minimum delta of principal amount. /// @param interest Minimum delta of interest. /// @param duration Minimum delta of duration. struct ImprovementMinimum { uint256 principalAmount; uint256 interest; uint256 duration; } /// @notice Arbitrary contract to validate offers implementing `IBaseOfferValidator`. /// @param validator Address of the validator contract. /// @param arguments Arguments to pass to the validator. struct OfferValidator { address validator; bytes arguments; } /// @notice Borrowers receive offers that are then validated. /// @dev Setting the nftCollateralTokenId to 0 triggers validation through `validators`. /// @param offerId Offer ID. Used for canceling/setting as executed. /// @param lender Lender of the offer. /// @param fee Origination fee. /// @param borrower Borrower of the offer. Can be set to 0 (any borrower). /// @param capacity Capacity of the offer. /// @param nftCollateralAddress Address of the NFT collateral. /// @param nftCollateralTokenId NFT collateral token ID. /// @param principalAddress Address of the principal. /// @param principalAmount Principal amount of the loan. /// @param aprBps APR in BPS. /// @param expirationTime Expiration time of the offer. /// @param duration Duration of the loan in seconds. /// @param validators Arbitrary contract to validate offers implementing `IBaseOfferValidator`. struct LoanOffer { uint256 offerId; address lender; uint256 fee; address borrower; uint256 capacity; address nftCollateralAddress; uint256 nftCollateralTokenId; address principalAddress; uint256 principalAmount; uint256 aprBps; uint256 expirationTime; uint256 duration; OfferValidator[] validators; } /// @notice Offer + necessary fields to execute a specific loan. This has a separate expirationTime to avoid /// someone holding an offer and executing much later, without the borrower's awareness. /// @param offer Loan offer. It can be executed potentially for multiple ids / amounts < principalAmount. /// @param tokenId NFT collateral token ID. /// @param amount The amount the borrower is willing to take (must be <= _loanOffer principalAmount) /// @param expirationTime Expiration time of the signed offer by the borrower. /// @param callbackData Data to pass to the callback. struct ExecutionData { LoanOffer offer; uint256 tokenId; uint256 amount; uint256 expirationTime; bytes callbackData; } /// @notice Recipient address and fraction of gains charged by the protocol. struct ProtocolFee { address recipient; uint256 fraction; } /// @notice Total number of loans issued by this contract. function getTotalLoansIssued() external view returns (uint256); /// @notice Cancel offer for `msg.sender`. Each lender has unique offerIds. /// @param _offerId Offer ID. function cancelOffer(uint256 _offerId) external; /// @notice Cancel multiple offers. /// @param _offerIds Offer IDs. function cancelOffers(uint256[] calldata _offerIds) external; /// @notice Cancell all offers with offerId < _minOfferId /// @param _minOfferId Minimum offer ID. function cancelAllOffers(uint256 _minOfferId) external; /// @notice Cancel renegotiation offer. Similar to offers. /// @param _renegotiationId Renegotiation offer ID. function cancelRenegotiationOffer(uint256 _renegotiationId) external; /// @notice Cancel multiple renegotiation offers. /// @param _renegotiationIds Renegotiation offer IDs. function cancelRenegotiationOffers(uint256[] calldata _renegotiationIds) external; /// @notice Cancell all renegotiation offers with renegotiationId < _minRenegotiationId /// @param _minRenegotiationId Minimum renegotiation offer ID. function cancelAllRenegotiationOffers(uint256 _minRenegotiationId) external; /// @return protocolFee The Protocol fee. function getProtocolFee() external view returns (ProtocolFee memory); /// @return pendingProtocolFee The pending protocol fee. function getPendingProtocolFee() external view returns (ProtocolFee memory); /// @return protocolFeeSetTime Time when the protocol fee was set to be changed. function getPendingProtocolFeeSetTime() external view returns (uint256); /// @notice Kicks off the process to update the protocol fee. /// @param _newProtocolFee New protocol fee. function updateProtocolFee(ProtocolFee calldata _newProtocolFee) external; /// @notice Set the protocol fee if enough notice has been given. function setProtocolFee() external; /// @return Liquidator contract address function getLiquidator() external returns (address); /// @notice Updates the liquidation contract. /// @param loanLiquidator New liquidation contract. function updateLiquidationContract(ILoanLiquidator loanLiquidator) external; /// @notice Updates the auction duration for liquidations. /// @param _newDuration New auction duration. function updateLiquidationAuctionDuration(uint48 _newDuration) external; /// @return auctionDuration Returns the auction's duration for liquidations. function getLiquidationAuctionDuration() external returns (uint48); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {IEIP712} from "./IEIP712.sol"; /// @title SignatureTransfer /// @notice Handles ERC20 token transfers through signature based actions /// @dev Requires user's token approval on the Permit2 contract interface ISignatureTransfer is IEIP712 { /// @notice Thrown when the requested amount for a transfer is larger than the permissioned amount /// @param maxAmount The maximum amount a spender can request to transfer error InvalidAmount(uint256 maxAmount); /// @notice Thrown when the number of tokens permissioned to a spender does not match the number of tokens being transferred /// @dev If the spender does not need to transfer the number of tokens permitted, the spender can request amount 0 to be transferred error LengthMismatch(); /// @notice Emits an event when the owner successfully invalidates an unordered nonce. event UnorderedNonceInvalidation(address indexed owner, uint256 word, uint256 mask); /// @notice The token and amount details for a transfer signed in the permit transfer signature struct TokenPermissions { // ERC20 token address address token; // the maximum amount that can be spent uint256 amount; } /// @notice The signed permit message for a single token transfer struct PermitTransferFrom { TokenPermissions permitted; // a unique value for every token owner's signature to prevent signature replays uint256 nonce; // deadline on the permit signature uint256 deadline; } /// @notice Specifies the recipient address and amount for batched transfers. /// @dev Recipients and amounts correspond to the index of the signed token permissions array. /// @dev Reverts if the requested amount is greater than the permitted signed amount. struct SignatureTransferDetails { // recipient address address to; // spender requested amount uint256 requestedAmount; } /// @notice Used to reconstruct the signed permit message for multiple token transfers /// @dev Do not need to pass in spender address as it is required that it is msg.sender /// @dev Note that a user still signs over a spender address struct PermitBatchTransferFrom { // the tokens and corresponding amounts permitted for a transfer TokenPermissions[] permitted; // a unique value for every token owner's signature to prevent signature replays uint256 nonce; // deadline on the permit signature uint256 deadline; } /// @notice A map from token owner address and a caller specified word index to a bitmap. Used to set bits in the bitmap to prevent against signature replay protection /// @dev Uses unordered nonces so that permit messages do not need to be spent in a certain order /// @dev The mapping is indexed first by the token owner, then by an index specified in the nonce /// @dev It returns a uint256 bitmap /// @dev The index, or wordPosition is capped at type(uint248).max function nonceBitmap(address, uint256) external view returns (uint256); /// @notice Transfers a token using a signed permit message /// @dev Reverts if the requested amount is greater than the permitted signed amount /// @param permit The permit data signed over by the owner /// @param owner The owner of the tokens to transfer /// @param transferDetails The spender's requested transfer details for the permitted token /// @param signature The signature to verify function permitTransferFrom( PermitTransferFrom memory permit, SignatureTransferDetails calldata transferDetails, address owner, bytes calldata signature ) external; /// @notice Transfers a token using a signed permit message /// @notice Includes extra data provided by the caller to verify signature over /// @dev The witness type string must follow EIP712 ordering of nested structs and must include the TokenPermissions type definition /// @dev Reverts if the requested amount is greater than the permitted signed amount /// @param permit The permit data signed over by the owner /// @param owner The owner of the tokens to transfer /// @param transferDetails The spender's requested transfer details for the permitted token /// @param witness Extra data to include when checking the user signature /// @param witnessTypeString The EIP-712 type definition for remaining string stub of the typehash /// @param signature The signature to verify function permitWitnessTransferFrom( PermitTransferFrom memory permit, SignatureTransferDetails calldata transferDetails, address owner, bytes32 witness, string calldata witnessTypeString, bytes calldata signature ) external; /// @notice Transfers multiple tokens using a signed permit message /// @param permit The permit data signed over by the owner /// @param owner The owner of the tokens to transfer /// @param transferDetails Specifies the recipient and requested amount for the token transfer /// @param signature The signature to verify function permitTransferFrom( PermitBatchTransferFrom memory permit, SignatureTransferDetails[] calldata transferDetails, address owner, bytes calldata signature ) external; /// @notice Transfers multiple tokens using a signed permit message /// @dev The witness type string must follow EIP712 ordering of nested structs and must include the TokenPermissions type definition /// @notice Includes extra data provided by the caller to verify signature over /// @param permit The permit data signed over by the owner /// @param owner The owner of the tokens to transfer /// @param transferDetails Specifies the recipient and requested amount for the token transfer /// @param witness Extra data to include when checking the user signature /// @param witnessTypeString The EIP-712 type definition for remaining string stub of the typehash /// @param signature The signature to verify function permitWitnessTransferFrom( PermitBatchTransferFrom memory permit, SignatureTransferDetails[] calldata transferDetails, address owner, bytes32 witness, string calldata witnessTypeString, bytes calldata signature ) external; /// @notice Invalidates the bits specified in mask for the bitmap at the word position /// @dev The wordPos is maxed at type(uint248).max /// @param wordPos A number to index the nonceBitmap at /// @param mask A bitmap masked against msg.sender's current bitmap at the word position function invalidateUnorderedNonces(uint256 wordPos, uint256 mask) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {IEIP712} from "./IEIP712.sol"; /// @title AllowanceTransfer /// @notice Handles ERC20 token permissions through signature based allowance setting and ERC20 token transfers by checking allowed amounts /// @dev Requires user's token approval on the Permit2 contract interface IAllowanceTransfer is IEIP712 { /// @notice Thrown when an allowance on a token has expired. /// @param deadline The timestamp at which the allowed amount is no longer valid error AllowanceExpired(uint256 deadline); /// @notice Thrown when an allowance on a token has been depleted. /// @param amount The maximum amount allowed error InsufficientAllowance(uint256 amount); /// @notice Thrown when too many nonces are invalidated. error ExcessiveInvalidation(); /// @notice Emits an event when the owner successfully invalidates an ordered nonce. event NonceInvalidation( address indexed owner, address indexed token, address indexed spender, uint48 newNonce, uint48 oldNonce ); /// @notice Emits an event when the owner successfully sets permissions on a token for the spender. event Approval( address indexed owner, address indexed token, address indexed spender, uint160 amount, uint48 expiration ); /// @notice Emits an event when the owner successfully sets permissions using a permit signature on a token for the spender. event Permit( address indexed owner, address indexed token, address indexed spender, uint160 amount, uint48 expiration, uint48 nonce ); /// @notice Emits an event when the owner sets the allowance back to 0 with the lockdown function. event Lockdown(address indexed owner, address token, address spender); /// @notice The permit data for a token struct PermitDetails { // ERC20 token address address token; // the maximum amount allowed to spend uint160 amount; // timestamp at which a spender's token allowances become invalid uint48 expiration; // an incrementing value indexed per owner,token,and spender for each signature uint48 nonce; } /// @notice The permit message signed for a single token allowance struct PermitSingle { // the permit data for a single token alownce PermitDetails details; // address permissioned on the allowed tokens address spender; // deadline on the permit signature uint256 sigDeadline; } /// @notice The permit message signed for multiple token allowances struct PermitBatch { // the permit data for multiple token allowances PermitDetails[] details; // address permissioned on the allowed tokens address spender; // deadline on the permit signature uint256 sigDeadline; } /// @notice The saved permissions /// @dev This info is saved per owner, per token, per spender and all signed over in the permit message /// @dev Setting amount to type(uint160).max sets an unlimited approval struct PackedAllowance { // amount allowed uint160 amount; // permission expiry uint48 expiration; // an incrementing value indexed per owner,token,and spender for each signature uint48 nonce; } /// @notice A token spender pair. struct TokenSpenderPair { // the token the spender is approved address token; // the spender address address spender; } /// @notice Details for a token transfer. struct AllowanceTransferDetails { // the owner of the token address from; // the recipient of the token address to; // the amount of the token uint160 amount; // the token to be transferred address token; } /// @notice A mapping from owner address to token address to spender address to PackedAllowance struct, which contains details and conditions of the approval. /// @notice The mapping is indexed in the above order see: allowance[ownerAddress][tokenAddress][spenderAddress] /// @dev The packed slot holds the allowed amount, expiration at which the allowed amount is no longer valid, and current nonce thats updated on any signature based approvals. function allowance(address user, address token, address spender) external view returns (uint160 amount, uint48 expiration, uint48 nonce); /// @notice Approves the spender to use up to amount of the specified token up until the expiration /// @param token The token to approve /// @param spender The spender address to approve /// @param amount The approved amount of the token /// @param expiration The timestamp at which the approval is no longer valid /// @dev The packed allowance also holds a nonce, which will stay unchanged in approve /// @dev Setting amount to type(uint160).max sets an unlimited approval function approve(address token, address spender, uint160 amount, uint48 expiration) external; /// @notice Permit a spender to a given amount of the owners token via the owner's EIP-712 signature /// @dev May fail if the owner's nonce was invalidated in-flight by invalidateNonce /// @param owner The owner of the tokens being approved /// @param permitSingle Data signed over by the owner specifying the terms of approval /// @param signature The owner's signature over the permit data function permit(address owner, PermitSingle memory permitSingle, bytes calldata signature) external; /// @notice Permit a spender to the signed amounts of the owners tokens via the owner's EIP-712 signature /// @dev May fail if the owner's nonce was invalidated in-flight by invalidateNonce /// @param owner The owner of the tokens being approved /// @param permitBatch Data signed over by the owner specifying the terms of approval /// @param signature The owner's signature over the permit data function permit(address owner, PermitBatch memory permitBatch, bytes calldata signature) external; /// @notice Transfer approved tokens from one address to another /// @param from The address to transfer from /// @param to The address of the recipient /// @param amount The amount of the token to transfer /// @param token The token address to transfer /// @dev Requires the from address to have approved at least the desired amount /// of tokens to msg.sender. function transferFrom(address from, address to, uint160 amount, address token) external; /// @notice Transfer approved tokens in a batch /// @param transferDetails Array of owners, recipients, amounts, and tokens for the transfers /// @dev Requires the from addresses to have approved at least the desired amount /// of tokens to msg.sender. function transferFrom(AllowanceTransferDetails[] calldata transferDetails) external; /// @notice Enables performing a "lockdown" of the sender's Permit2 identity /// by batch revoking approvals /// @param approvals Array of approvals to revoke. function lockdown(TokenSpenderPair[] calldata approvals) external; /// @notice Invalidate nonces for a given (token, spender) pair /// @param token The token to invalidate nonces for /// @param spender The spender to invalidate nonces for /// @param newNonce The new nonce to set. Invalidates all nonces less than it. /// @dev Can't invalidate more than 2**16 nonces per transaction. function invalidateNonces(address token, address spender, uint48 newNonce) external; }
// SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.20; import "../../interfaces/ILoanLiquidator.sol"; /// @title Interface for Loans. /// @author Florida St /// @notice Basic Loan interface IBaseLoan { /// @notice Minimum improvement (in BPS) required for a strict improvement. /// @param principalAmount Minimum delta of principal amount. /// @param interest Minimum delta of interest. /// @param duration Minimum delta of duration. struct ImprovementMinimum { uint256 principalAmount; uint256 interest; uint256 duration; } /// @notice Arbitrary contract to validate offers implementing `IBaseOfferValidator`. /// @param validator Address of the validator contract. /// @param arguments Arguments to pass to the validator. struct OfferValidator { address validator; bytes arguments; } /// @notice Total number of loans issued by this contract. function getTotalLoansIssued() external view returns (uint256); /// @notice Cancel offer for `msg.sender`. Each lender has unique offerIds. /// @param _offerId Offer ID. function cancelOffer(uint256 _offerId) external; /// @notice Cancell all offers with offerId < _minOfferId /// @param _minOfferId Minimum offer ID. function cancelAllOffers(uint256 _minOfferId) external; /// @notice Cancel renegotiation offer. Similar to offers. /// @param _renegotiationId Renegotiation offer ID. function cancelRenegotiationOffer(uint256 _renegotiationId) external; }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol) pragma solidity ^0.8.20; /** * @dev Interface of the ERC-165 standard, as defined in the * https://eips.ethereum.org/EIPS/eip-165[ERC]. * * Implementers can declare support of contract interfaces, which can then be * queried by others ({ERC165Checker}). * * For an implementation, see {ERC165}. */ interface IERC165 { /** * @dev Returns true if this contract implements the interface defined by * `interfaceId`. See the corresponding * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section] * to learn more about how these ids are created. * * This function call must use less than 30 000 gas. */ function supportsInterface(bytes4 interfaceId) external view returns (bool); }
// SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.20; import "./loans/IMultiSourceLoan.sol"; /// @title Liquidates Collateral for Defaulted Loans using English Auctions. /// @author Florida St /// @notice It liquidates collateral corresponding to defaulted loans /// and sends back the proceeds to the loan contract for distribution. interface IAuctionLoanLiquidator { /// @notice The auction struct. /// @param loanAddress The loan contract address. /// @param loanId The loan id. /// @param highestBid The highest bid. /// @param triggerFee The trigger fee. /// @param minBid The minimum bid. /// @param highestBidder The highest bidder. /// @param duration The auction duration. /// @param asset The asset address. /// @param startTime The auction start time. /// @param originator The address that triggered the liquidation. /// @param lastBidTime The last bid time. struct Auction { address loanAddress; uint256 loanId; uint256 highestBid; uint256 triggerFee; uint256 minBid; address highestBidder; uint96 duration; address asset; uint96 startTime; address originator; uint96 lastBidTime; } /// @notice Add a loan contract to the list of accepted contracts. /// @param _loanContract The loan contract to be added. function addLoanContract(address _loanContract) external; /// @notice Remove a loan contract from the list of accepted contracts. /// @param _loanContract The loan contract to be removed. function removeLoanContract(address _loanContract) external; /// @return The loan contracts that are accepted by this liquidator. function getValidLoanContracts() external view returns (address[] memory); /// @notice Update liquidation distributor. /// @param _liquidationDistributor The new liquidation distributor. function updateLiquidationDistributor(address _liquidationDistributor) external; /// @return liquidationDistributor The liquidation distributor address. function getLiquidationDistributor() external view returns (address); /// @notice Called by the owner to update the trigger fee. /// @param triggerFee The new trigger fee. function updateTriggerFee(uint256 triggerFee) external; /// @return triggerFee The trigger fee. function getTriggerFee() external view returns (uint256); /// @notice When a bid is placed, the contract takes possesion of the bid, and /// if there was a previous bid, it returns that capital to the original /// bidder. /// @param _contract The nft contract address. /// @param _tokenId The nft id. /// @param _auction The auction struct. /// @param _bid The bid amount. /// @return auction The updated auction struct. function placeBid(address _contract, uint256 _tokenId, Auction memory _auction, uint256 _bid) external returns (Auction memory); /// @notice On settlement, the NFT is sent to the highest bidder. /// Calls loan liquidated for accounting purposes. /// @param _auction The auction struct. /// @param _loan The loan struct. function settleAuction(Auction calldata _auction, IMultiSourceLoan.Loan calldata _loan) external; /// @notice The contract has hashes of all auctions to save space (not the actual struct) /// @param _contract The nft contract address. /// @param _tokenId The nft id. /// @return auctionHash The auction hash. function getAuctionHash(address _contract, uint256 _tokenId) external view returns (bytes32); }
// SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.20; interface IMulticall { error MulticallFailed(uint256 i, bytes returndata); /// @notice Call multiple functions in the contract. Revert if one of them fails, return results otherwise. /// @param data Encoded function calls. /// @return results The results of the function calls. function multicall(bytes[] calldata data) external payable returns (bytes[] memory results); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1271.sol) pragma solidity ^0.8.20; /** * @dev Interface of the ERC-1271 standard signature validation method for * contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271]. */ interface IERC1271 { /** * @dev Should return whether the signature provided is valid for the provided data * @param hash Hash of the data to be signed * @param signature Signature byte array associated with _data */ function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue); }
// SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.21; import "@solmate/auth/Owned.sol"; import "@solmate/tokens/ERC721.sol"; import "@solmate/utils/FixedPointMathLib.sol"; import "@solmate/utils/ReentrancyGuard.sol"; import "../interfaces/ILiquidationHandler.sol"; import "../interfaces/loans/IMultiSourceLoan.sol"; import "./callbacks/CallbackHandler.sol"; import "./InputChecker.sol"; /// @title Liquidation Handler /// @author Florida St /// @notice Liquidation Handler for defaulted loans abstract contract LiquidationHandler is ILiquidationHandler, ReentrancyGuard, CallbackHandler { using InputChecker for address; using FixedPointMathLib for uint256; uint48 public constant MIN_AUCTION_DURATION = 3 days; uint48 public constant MAX_AUCTION_DURATION = 7 days; uint256 public constant MIN_BID_LIQUIDATION = 50; uint256 private constant _BPS = 10000; /// @notice Duration of the auction when a loan defaults requires a liquidation. uint48 internal _liquidationAuctionDuration = 3 days; /// @notice Liquidator used defaulted loans that requires liquidation. address internal _loanLiquidator; event MinBidLiquidationUpdated(uint256 newMinBid); event LoanSentToLiquidator(uint256 loanId, address liquidator); event LoanForeclosed(uint256 loanId); event LiquidationContractUpdated(address liquidator); event LiquidationAuctionDurationUpdated(uint256 newDuration); error LiquidatorOnlyError(address _liquidator); error LoanNotDueError(uint256 _expirationTime); error InvalidDurationError(); /// @notice Constructor /// @param __owner The owner of the contract /// @param _updateWaitTime The time to wait before a new owner can be set /// @param __loanLiquidator The liquidator contract /// @param __protocolFee The protocol fee constructor(address __owner, uint256 _updateWaitTime, address __loanLiquidator, ProtocolFee memory __protocolFee) CallbackHandler(__owner, _updateWaitTime, __protocolFee) { __loanLiquidator.checkNotZero(); _loanLiquidator = __loanLiquidator; } modifier onlyLiquidator() { if (msg.sender != address(_loanLiquidator)) { revert LiquidatorOnlyError(address(_loanLiquidator)); } _; } /// @inheritdoc ILiquidationHandler function getLiquidator() external view override returns (address) { return _loanLiquidator; } /// @inheritdoc ILiquidationHandler function updateLiquidationContract(address __loanLiquidator) external override onlyOwner { __loanLiquidator.checkNotZero(); _loanLiquidator = __loanLiquidator; emit LiquidationContractUpdated(__loanLiquidator); } /// @inheritdoc ILiquidationHandler function updateLiquidationAuctionDuration(uint48 _newDuration) external override onlyOwner { if (_newDuration < MIN_AUCTION_DURATION || _newDuration > MAX_AUCTION_DURATION) { revert InvalidDurationError(); } _liquidationAuctionDuration = _newDuration; emit LiquidationAuctionDurationUpdated(_newDuration); } /// @inheritdoc ILiquidationHandler function getLiquidationAuctionDuration() external view override returns (uint48) { return _liquidationAuctionDuration; } function _liquidateLoan(uint256 _loanId, IMultiSourceLoan.Loan calldata _loan, bool _canClaim) internal returns (bool liquidated, bytes memory liquidation) { uint256 expirationTime; unchecked { expirationTime = _loan.startTime + _loan.duration; } if (expirationTime > block.timestamp) { revert LoanNotDueError(expirationTime); } if (_canClaim) { ERC721(_loan.nftCollateralAddress).transferFrom( address(this), _loan.tranche[0].lender, _loan.nftCollateralTokenId ); emit LoanForeclosed(_loanId); liquidated = true; } else { address liquidator = _loanLiquidator; ERC721(_loan.nftCollateralAddress).transferFrom(address(this), liquidator, _loan.nftCollateralTokenId); liquidation = ILoanLiquidator(liquidator).liquidateLoan( _loanId, _loan.nftCollateralAddress, _loan.nftCollateralTokenId, _loan.principalAddress, _liquidationAuctionDuration, _loan.principalAmount.mulDivDown(MIN_BID_LIQUIDATION, _BPS), msg.sender ); emit LoanSentToLiquidator(_loanId, liquidator); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/Strings.sol) pragma solidity ^0.8.20; import {Math} from "./math/Math.sol"; import {SignedMath} from "./math/SignedMath.sol"; /** * @dev String operations. */ library Strings { bytes16 private constant HEX_DIGITS = "0123456789abcdef"; uint8 private constant ADDRESS_LENGTH = 20; /** * @dev The `value` string doesn't fit in the specified `length`. */ error StringsInsufficientHexLength(uint256 value, uint256 length); /** * @dev Converts a `uint256` to its ASCII `string` decimal representation. */ function toString(uint256 value) internal pure returns (string memory) { unchecked { uint256 length = Math.log10(value) + 1; string memory buffer = new string(length); uint256 ptr; /// @solidity memory-safe-assembly assembly { ptr := add(buffer, add(32, length)) } while (true) { ptr--; /// @solidity memory-safe-assembly assembly { mstore8(ptr, byte(mod(value, 10), HEX_DIGITS)) } value /= 10; if (value == 0) break; } return buffer; } } /** * @dev Converts a `int256` to its ASCII `string` decimal representation. */ function toStringSigned(int256 value) internal pure returns (string memory) { return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value))); } /** * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. */ function toHexString(uint256 value) internal pure returns (string memory) { unchecked { return toHexString(value, Math.log256(value) + 1); } } /** * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. */ function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { uint256 localValue = value; bytes memory buffer = new bytes(2 * length + 2); buffer[0] = "0"; buffer[1] = "x"; for (uint256 i = 2 * length + 1; i > 1; --i) { buffer[i] = HEX_DIGITS[localValue & 0xf]; localValue >>= 4; } if (localValue != 0) { revert StringsInsufficientHexLength(value, length); } return string(buffer); } /** * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal * representation. */ function toHexString(address addr) internal pure returns (string memory) { return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH); } /** * @dev Returns true if the two strings are equal. */ function equal(string memory a, string memory b) internal pure returns (bool) { return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b)); } }
// SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.20; /// @title Liquidates Collateral for Defaulted Loans /// @author Florida St /// @notice It liquidates collateral corresponding to defaulted loans /// and sends back the proceeds to the loan contract for distribution. interface ILoanLiquidator { /// @notice Given a loan, it takes posession of the NFT and liquidates it. /// @param _loanId The loan id. /// @param _contract The loan contract address. /// @param _tokenId The NFT id. /// @param _asset The asset address. /// @param _duration The liquidation duration. /// @param _originator The address that trigger the liquidation. /// @return encodedAuction Encoded struct. function liquidateLoan( uint256 _loanId, address _contract, uint256 _tokenId, address _asset, uint96 _duration, address _originator ) external returns (bytes memory); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface IEIP712 { function DOMAIN_SEPARATOR() external view returns (bytes32); }
// SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.20; import "../interfaces/loans/IMultiSourceLoan.sol"; /// @title Liquidates Collateral for Defaulted Loans /// @author Florida St /// @notice It liquidates collateral corresponding to defaulted loans /// and sends back the proceeds to the loan contract for distribution. interface ILoanLiquidator { /// @notice Given a loan, it takes posession of the NFT and liquidates it. /// @param _loanId The loan id. /// @param _contract The loan contract address. /// @param _tokenId The NFT id. /// @param _asset The asset address. /// @param _duration The liquidation duration. /// @param _minBid The minimum bid. /// @param _originator The address that trigger the liquidation. /// @return encodedAuction Encoded struct. function liquidateLoan( uint256 _loanId, address _contract, uint256 _tokenId, address _asset, uint96 _duration, uint256 _minBid, address _originator ) external returns (bytes memory); }
// SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.21; import "./loans/IMultiSourceLoan.sol"; /// @title Interface for liquidation handlers. /// @author Florida St /// @notice Liquidation Handler interface ILiquidationHandler { /// @return Liquidator contract address function getLiquidator() external returns (address); /// @notice Updates the liquidation contract. /// @param loanLiquidator New liquidation contract. function updateLiquidationContract(address loanLiquidator) external; /// @notice Updates the auction duration for liquidations. /// @param _newDuration New auction duration. function updateLiquidationAuctionDuration(uint48 _newDuration) external; /// @return auctionDuration Returns the auction's duration for liquidations. function getLiquidationAuctionDuration() external returns (uint48); }
// SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.21; import "../utils/TwoStepOwned.sol"; import "../InputChecker.sol"; import "../utils/WithProtocolFee.sol"; import "../../interfaces/callbacks/ILoanCallback.sol"; /// @title CallbackHandler /// @author Florida St /// @notice Handle callbacks from the MultiSourceLoan contract. abstract contract CallbackHandler is WithProtocolFee { using InputChecker for address; /// @notice For security reasons we only allow a whitelisted set of callback contracts. mapping(address callbackContract => bool isWhitelisted) internal _isWhitelistedCallbackContract; address private immutable _multiSourceLoan; event WhitelistedCallbackContractAdded(address contractAdded); event WhitelistedCallbackContractRemoved(address contractRemoved); constructor(address __owner, uint256 _minWaitTime, ProtocolFee memory __protocolFee) WithProtocolFee(__owner, _minWaitTime, __protocolFee) {} /// @notice Add a whitelisted callback contract. /// @param _contract Address of the contract. function addWhitelistedCallbackContract(address _contract) external onlyOwner { _contract.checkNotZero(); _isWhitelistedCallbackContract[_contract] = true; emit WhitelistedCallbackContractAdded(_contract); } /// @notice Remove a whitelisted callback contract. /// @param _contract Address of the contract. function removeWhitelistedCallbackContract(address _contract) external onlyOwner { _isWhitelistedCallbackContract[_contract] = false; emit WhitelistedCallbackContractRemoved(_contract); } /// @return Whether a callback contract is whitelisted function isWhitelistedCallbackContract(address _contract) external view returns (bool) { return _isWhitelistedCallbackContract[_contract]; } /// @notice Handle the afterPrincipalTransfer callback. /// @param _loan Loan. /// @param _callbackAddress Callback address. /// @param _callbackData Callback data. /// @param _fee Fee. function handleAfterPrincipalTransferCallback( IMultiSourceLoan.Loan memory _loan, address _callbackAddress, bytes memory _callbackData, uint256 _fee ) internal { if ( !_isWhitelistedCallbackContract[_callbackAddress] || ILoanCallback(_callbackAddress).afterPrincipalTransfer(_loan, _fee, _callbackData) != ILoanCallback.afterPrincipalTransfer.selector ) { revert ILoanCallback.InvalidCallbackError(); } } /// @notice Handle the afterNFTTransfer callback. /// @param _loan Loan. /// @param _callbackAddress Callback address. /// @param _callbackData Callback data. function handleAfterNFTTransferCallback( IMultiSourceLoan.Loan memory _loan, address _callbackAddress, bytes calldata _callbackData ) internal { if ( !_isWhitelistedCallbackContract[_callbackAddress] || ILoanCallback(_callbackAddress).afterNFTTransfer(_loan, _callbackData) != ILoanCallback.afterNFTTransfer.selector ) { revert ILoanCallback.InvalidCallbackError(); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol) pragma solidity ^0.8.20; import {Panic} from "../Panic.sol"; import {SafeCast} from "./SafeCast.sol"; /** * @dev Standard math utilities missing in the Solidity language. */ library Math { enum Rounding { Floor, // Toward negative infinity Ceil, // Toward positive infinity Trunc, // Toward zero Expand // Away from zero } /** * @dev Returns the addition of two unsigned integers, with an success flag (no overflow). */ function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) { unchecked { uint256 c = a + b; if (c < a) return (false, 0); return (true, c); } } /** * @dev Returns the subtraction of two unsigned integers, with an success flag (no overflow). */ function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) { unchecked { if (b > a) return (false, 0); return (true, a - b); } } /** * @dev Returns the multiplication of two unsigned integers, with an success flag (no overflow). */ function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) { unchecked { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 if (a == 0) return (true, 0); uint256 c = a * b; if (c / a != b) return (false, 0); return (true, c); } } /** * @dev Returns the division of two unsigned integers, with a success flag (no division by zero). */ function tryDiv(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) { unchecked { if (b == 0) return (false, 0); return (true, a / b); } } /** * @dev Returns the remainder of dividing two unsigned integers, with a success flag (no division by zero). */ function tryMod(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) { unchecked { if (b == 0) return (false, 0); return (true, a % b); } } /** * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant. * * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone. * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute * one branch when needed, making this function more expensive. */ function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256) { unchecked { // branchless ternary works because: // b ^ (a ^ b) == a // b ^ 0 == b return b ^ ((a ^ b) * SafeCast.toUint(condition)); } } /** * @dev Returns the largest of two numbers. */ function max(uint256 a, uint256 b) internal pure returns (uint256) { return ternary(a > b, a, b); } /** * @dev Returns the smallest of two numbers. */ function min(uint256 a, uint256 b) internal pure returns (uint256) { return ternary(a < b, a, b); } /** * @dev Returns the average of two numbers. The result is rounded towards * zero. */ function average(uint256 a, uint256 b) internal pure returns (uint256) { // (a + b) / 2 can overflow. return (a & b) + (a ^ b) / 2; } /** * @dev Returns the ceiling of the division of two numbers. * * This differs from standard division with `/` in that it rounds towards infinity instead * of rounding towards zero. */ function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { if (b == 0) { // Guarantee the same behavior as in a regular Solidity division. Panic.panic(Panic.DIVISION_BY_ZERO); } // The following calculation ensures accurate ceiling division without overflow. // Since a is non-zero, (a - 1) / b will not overflow. // The largest possible result occurs when (a - 1) / b is type(uint256).max, // but the largest value we can obtain is type(uint256).max - 1, which happens // when a = type(uint256).max and b = 1. unchecked { return SafeCast.toUint(a > 0) * ((a - 1) / b + 1); } } /** * @dev Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or * denominator == 0. * * Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by * Uniswap Labs also under MIT license. */ function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { unchecked { // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2²⁵⁶ and mod 2²⁵⁶ - 1, then use // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 // variables such that product = prod1 * 2²⁵⁶ + prod0. uint256 prod0 = x * y; // Least significant 256 bits of the product uint256 prod1; // Most significant 256 bits of the product assembly { let mm := mulmod(x, y, not(0)) prod1 := sub(sub(mm, prod0), lt(mm, prod0)) } // Handle non-overflow cases, 256 by 256 division. if (prod1 == 0) { // Solidity will revert if denominator == 0, unlike the div opcode on its own. // The surrounding unchecked block does not change this fact. // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic. return prod0 / denominator; } // Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0. if (denominator <= prod1) { Panic.panic(ternary(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW)); } /////////////////////////////////////////////// // 512 by 256 division. /////////////////////////////////////////////// // Make division exact by subtracting the remainder from [prod1 prod0]. uint256 remainder; assembly { // Compute remainder using mulmod. remainder := mulmod(x, y, denominator) // Subtract 256 bit number from 512 bit number. prod1 := sub(prod1, gt(remainder, prod0)) prod0 := sub(prod0, remainder) } // Factor powers of two out of denominator and compute largest power of two divisor of denominator. // Always >= 1. See https://cs.stackexchange.com/q/138556/92363. uint256 twos = denominator & (0 - denominator); assembly { // Divide denominator by twos. denominator := div(denominator, twos) // Divide [prod1 prod0] by twos. prod0 := div(prod0, twos) // Flip twos such that it is 2²⁵⁶ / twos. If twos is zero, then it becomes one. twos := add(div(sub(0, twos), twos), 1) } // Shift in bits from prod1 into prod0. prod0 |= prod1 * twos; // Invert denominator mod 2²⁵⁶. Now that denominator is an odd number, it has an inverse modulo 2²⁵⁶ such // that denominator * inv ≡ 1 mod 2²⁵⁶. Compute the inverse by starting with a seed that is correct for // four bits. That is, denominator * inv ≡ 1 mod 2⁴. uint256 inverse = (3 * denominator) ^ 2; // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also // works in modular arithmetic, doubling the correct bits in each step. inverse *= 2 - denominator * inverse; // inverse mod 2⁸ inverse *= 2 - denominator * inverse; // inverse mod 2¹⁶ inverse *= 2 - denominator * inverse; // inverse mod 2³² inverse *= 2 - denominator * inverse; // inverse mod 2⁶⁴ inverse *= 2 - denominator * inverse; // inverse mod 2¹²⁸ inverse *= 2 - denominator * inverse; // inverse mod 2²⁵⁶ // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. // This will give us the correct result modulo 2²⁵⁶. Since the preconditions guarantee that the outcome is // less than 2²⁵⁶, this is the final result. We don't need to compute the high bits of the result and prod1 // is no longer required. result = prod0 * inverse; return result; } } /** * @dev Calculates x * y / denominator with full precision, following the selected rounding direction. */ function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { return mulDiv(x, y, denominator) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0); } /** * @dev Calculate the modular multiplicative inverse of a number in Z/nZ. * * If n is a prime, then Z/nZ is a field. In that case all elements are inversible, expect 0. * If n is not a prime, then Z/nZ is not a field, and some elements might not be inversible. * * If the input value is not inversible, 0 is returned. * * NOTE: If you know for sure that n is (big) a prime, it may be cheaper to use Ferma's little theorem and get the * inverse using `Math.modExp(a, n - 2, n)`. */ function invMod(uint256 a, uint256 n) internal pure returns (uint256) { unchecked { if (n == 0) return 0; // The inverse modulo is calculated using the Extended Euclidean Algorithm (iterative version) // Used to compute integers x and y such that: ax + ny = gcd(a, n). // When the gcd is 1, then the inverse of a modulo n exists and it's x. // ax + ny = 1 // ax = 1 + (-y)n // ax ≡ 1 (mod n) # x is the inverse of a modulo n // If the remainder is 0 the gcd is n right away. uint256 remainder = a % n; uint256 gcd = n; // Therefore the initial coefficients are: // ax + ny = gcd(a, n) = n // 0a + 1n = n int256 x = 0; int256 y = 1; while (remainder != 0) { uint256 quotient = gcd / remainder; (gcd, remainder) = ( // The old remainder is the next gcd to try. remainder, // Compute the next remainder. // Can't overflow given that (a % gcd) * (gcd // (a % gcd)) <= gcd // where gcd is at most n (capped to type(uint256).max) gcd - remainder * quotient ); (x, y) = ( // Increment the coefficient of a. y, // Decrement the coefficient of n. // Can overflow, but the result is casted to uint256 so that the // next value of y is "wrapped around" to a value between 0 and n - 1. x - y * int256(quotient) ); } if (gcd != 1) return 0; // No inverse exists. return ternary(x < 0, n - uint256(-x), uint256(x)); // Wrap the result if it's negative. } } /** * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m) * * Requirements: * - modulus can't be zero * - underlying staticcall to precompile must succeed * * IMPORTANT: The result is only valid if the underlying call succeeds. When using this function, make * sure the chain you're using it on supports the precompiled contract for modular exponentiation * at address 0x05 as specified in https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise, * the underlying function will succeed given the lack of a revert, but the result may be incorrectly * interpreted as 0. */ function modExp(uint256 b, uint256 e, uint256 m) internal view returns (uint256) { (bool success, uint256 result) = tryModExp(b, e, m); if (!success) { Panic.panic(Panic.DIVISION_BY_ZERO); } return result; } /** * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m). * It includes a success flag indicating if the operation succeeded. Operation will be marked has failed if trying * to operate modulo 0 or if the underlying precompile reverted. * * IMPORTANT: The result is only valid if the success flag is true. When using this function, make sure the chain * you're using it on supports the precompiled contract for modular exponentiation at address 0x05 as specified in * https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise, the underlying function will succeed given the lack * of a revert, but the result may be incorrectly interpreted as 0. */ function tryModExp(uint256 b, uint256 e, uint256 m) internal view returns (bool success, uint256 result) { if (m == 0) return (false, 0); /// @solidity memory-safe-assembly assembly { let ptr := mload(0x40) // | Offset | Content | Content (Hex) | // |-----------|------------|--------------------------------------------------------------------| // | 0x00:0x1f | size of b | 0x0000000000000000000000000000000000000000000000000000000000000020 | // | 0x20:0x3f | size of e | 0x0000000000000000000000000000000000000000000000000000000000000020 | // | 0x40:0x5f | size of m | 0x0000000000000000000000000000000000000000000000000000000000000020 | // | 0x60:0x7f | value of b | 0x<.............................................................b> | // | 0x80:0x9f | value of e | 0x<.............................................................e> | // | 0xa0:0xbf | value of m | 0x<.............................................................m> | mstore(ptr, 0x20) mstore(add(ptr, 0x20), 0x20) mstore(add(ptr, 0x40), 0x20) mstore(add(ptr, 0x60), b) mstore(add(ptr, 0x80), e) mstore(add(ptr, 0xa0), m) // Given the result < m, it's guaranteed to fit in 32 bytes, // so we can use the memory scratch space located at offset 0. success := staticcall(gas(), 0x05, ptr, 0xc0, 0x00, 0x20) result := mload(0x00) } } /** * @dev Variant of {modExp} that supports inputs of arbitrary length. */ function modExp(bytes memory b, bytes memory e, bytes memory m) internal view returns (bytes memory) { (bool success, bytes memory result) = tryModExp(b, e, m); if (!success) { Panic.panic(Panic.DIVISION_BY_ZERO); } return result; } /** * @dev Variant of {tryModExp} that supports inputs of arbitrary length. */ function tryModExp( bytes memory b, bytes memory e, bytes memory m ) internal view returns (bool success, bytes memory result) { if (_zeroBytes(m)) return (false, new bytes(0)); uint256 mLen = m.length; // Encode call args in result and move the free memory pointer result = abi.encodePacked(b.length, e.length, mLen, b, e, m); /// @solidity memory-safe-assembly assembly { let dataPtr := add(result, 0x20) // Write result on top of args to avoid allocating extra memory. success := staticcall(gas(), 0x05, dataPtr, mload(result), dataPtr, mLen) // Overwrite the length. // result.length > returndatasize() is guaranteed because returndatasize() == m.length mstore(result, mLen) // Set the memory pointer after the returned data. mstore(0x40, add(dataPtr, mLen)) } } /** * @dev Returns whether the provided byte array is zero. */ function _zeroBytes(bytes memory byteArray) private pure returns (bool) { for (uint256 i = 0; i < byteArray.length; ++i) { if (byteArray[i] != 0) { return false; } } return true; } /** * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded * towards zero. * * This method is based on Newton's method for computing square roots; the algorithm is restricted to only * using integer operations. */ function sqrt(uint256 a) internal pure returns (uint256) { unchecked { // Take care of easy edge cases when a == 0 or a == 1 if (a <= 1) { return a; } // In this function, we use Newton's method to get a root of `f(x) := x² - a`. It involves building a // sequence x_n that converges toward sqrt(a). For each iteration x_n, we also define the error between // the current value as `ε_n = | x_n - sqrt(a) |`. // // For our first estimation, we consider `e` the smallest power of 2 which is bigger than the square root // of the target. (i.e. `2**(e-1) ≤ sqrt(a) < 2**e`). We know that `e ≤ 128` because `(2¹²⁸)² = 2²⁵⁶` is // bigger than any uint256. // // By noticing that // `2**(e-1) ≤ sqrt(a) < 2**e → (2**(e-1))² ≤ a < (2**e)² → 2**(2*e-2) ≤ a < 2**(2*e)` // we can deduce that `e - 1` is `log2(a) / 2`. We can thus compute `x_n = 2**(e-1)` using a method similar // to the msb function. uint256 aa = a; uint256 xn = 1; if (aa >= (1 << 128)) { aa >>= 128; xn <<= 64; } if (aa >= (1 << 64)) { aa >>= 64; xn <<= 32; } if (aa >= (1 << 32)) { aa >>= 32; xn <<= 16; } if (aa >= (1 << 16)) { aa >>= 16; xn <<= 8; } if (aa >= (1 << 8)) { aa >>= 8; xn <<= 4; } if (aa >= (1 << 4)) { aa >>= 4; xn <<= 2; } if (aa >= (1 << 2)) { xn <<= 1; } // We now have x_n such that `x_n = 2**(e-1) ≤ sqrt(a) < 2**e = 2 * x_n`. This implies ε_n ≤ 2**(e-1). // // We can refine our estimation by noticing that the middle of that interval minimizes the error. // If we move x_n to equal 2**(e-1) + 2**(e-2), then we reduce the error to ε_n ≤ 2**(e-2). // This is going to be our x_0 (and ε_0) xn = (3 * xn) >> 1; // ε_0 := | x_0 - sqrt(a) | ≤ 2**(e-2) // From here, Newton's method give us: // x_{n+1} = (x_n + a / x_n) / 2 // // One should note that: // x_{n+1}² - a = ((x_n + a / x_n) / 2)² - a // = ((x_n² + a) / (2 * x_n))² - a // = (x_n⁴ + 2 * a * x_n² + a²) / (4 * x_n²) - a // = (x_n⁴ + 2 * a * x_n² + a² - 4 * a * x_n²) / (4 * x_n²) // = (x_n⁴ - 2 * a * x_n² + a²) / (4 * x_n²) // = (x_n² - a)² / (2 * x_n)² // = ((x_n² - a) / (2 * x_n))² // ≥ 0 // Which proves that for all n ≥ 1, sqrt(a) ≤ x_n // // This gives us the proof of quadratic convergence of the sequence: // ε_{n+1} = | x_{n+1} - sqrt(a) | // = | (x_n + a / x_n) / 2 - sqrt(a) | // = | (x_n² + a - 2*x_n*sqrt(a)) / (2 * x_n) | // = | (x_n - sqrt(a))² / (2 * x_n) | // = | ε_n² / (2 * x_n) | // = ε_n² / | (2 * x_n) | // // For the first iteration, we have a special case where x_0 is known: // ε_1 = ε_0² / | (2 * x_0) | // ≤ (2**(e-2))² / (2 * (2**(e-1) + 2**(e-2))) // ≤ 2**(2*e-4) / (3 * 2**(e-1)) // ≤ 2**(e-3) / 3 // ≤ 2**(e-3-log2(3)) // ≤ 2**(e-4.5) // // For the following iterations, we use the fact that, 2**(e-1) ≤ sqrt(a) ≤ x_n: // ε_{n+1} = ε_n² / | (2 * x_n) | // ≤ (2**(e-k))² / (2 * 2**(e-1)) // ≤ 2**(2*e-2*k) / 2**e // ≤ 2**(e-2*k) xn = (xn + a / xn) >> 1; // ε_1 := | x_1 - sqrt(a) | ≤ 2**(e-4.5) -- special case, see above xn = (xn + a / xn) >> 1; // ε_2 := | x_2 - sqrt(a) | ≤ 2**(e-9) -- general case with k = 4.5 xn = (xn + a / xn) >> 1; // ε_3 := | x_3 - sqrt(a) | ≤ 2**(e-18) -- general case with k = 9 xn = (xn + a / xn) >> 1; // ε_4 := | x_4 - sqrt(a) | ≤ 2**(e-36) -- general case with k = 18 xn = (xn + a / xn) >> 1; // ε_5 := | x_5 - sqrt(a) | ≤ 2**(e-72) -- general case with k = 36 xn = (xn + a / xn) >> 1; // ε_6 := | x_6 - sqrt(a) | ≤ 2**(e-144) -- general case with k = 72 // Because e ≤ 128 (as discussed during the first estimation phase), we know have reached a precision // ε_6 ≤ 2**(e-144) < 1. Given we're operating on integers, then we can ensure that xn is now either // sqrt(a) or sqrt(a) + 1. return xn - SafeCast.toUint(xn > a / xn); } } /** * @dev Calculates sqrt(a), following the selected rounding direction. */ function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = sqrt(a); return result + SafeCast.toUint(unsignedRoundsUp(rounding) && result * result < a); } } /** * @dev Return the log in base 2 of a positive value rounded towards zero. * Returns 0 if given 0. */ function log2(uint256 value) internal pure returns (uint256) { uint256 result = 0; uint256 exp; unchecked { exp = 128 * SafeCast.toUint(value > (1 << 128) - 1); value >>= exp; result += exp; exp = 64 * SafeCast.toUint(value > (1 << 64) - 1); value >>= exp; result += exp; exp = 32 * SafeCast.toUint(value > (1 << 32) - 1); value >>= exp; result += exp; exp = 16 * SafeCast.toUint(value > (1 << 16) - 1); value >>= exp; result += exp; exp = 8 * SafeCast.toUint(value > (1 << 8) - 1); value >>= exp; result += exp; exp = 4 * SafeCast.toUint(value > (1 << 4) - 1); value >>= exp; result += exp; exp = 2 * SafeCast.toUint(value > (1 << 2) - 1); value >>= exp; result += exp; result += SafeCast.toUint(value > 1); } return result; } /** * @dev Return the log in base 2, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log2(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log2(value); return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << result < value); } } /** * @dev Return the log in base 10 of a positive value rounded towards zero. * Returns 0 if given 0. */ function log10(uint256 value) internal pure returns (uint256) { uint256 result = 0; unchecked { if (value >= 10 ** 64) { value /= 10 ** 64; result += 64; } if (value >= 10 ** 32) { value /= 10 ** 32; result += 32; } if (value >= 10 ** 16) { value /= 10 ** 16; result += 16; } if (value >= 10 ** 8) { value /= 10 ** 8; result += 8; } if (value >= 10 ** 4) { value /= 10 ** 4; result += 4; } if (value >= 10 ** 2) { value /= 10 ** 2; result += 2; } if (value >= 10 ** 1) { result += 1; } } return result; } /** * @dev Return the log in base 10, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log10(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log10(value); return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 10 ** result < value); } } /** * @dev Return the log in base 256 of a positive value rounded towards zero. * Returns 0 if given 0. * * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. */ function log256(uint256 value) internal pure returns (uint256) { uint256 result = 0; uint256 isGt; unchecked { isGt = SafeCast.toUint(value > (1 << 128) - 1); value >>= isGt * 128; result += isGt * 16; isGt = SafeCast.toUint(value > (1 << 64) - 1); value >>= isGt * 64; result += isGt * 8; isGt = SafeCast.toUint(value > (1 << 32) - 1); value >>= isGt * 32; result += isGt * 4; isGt = SafeCast.toUint(value > (1 << 16) - 1); value >>= isGt * 16; result += isGt * 2; result += SafeCast.toUint(value > (1 << 8) - 1); } return result; } /** * @dev Return the log in base 256, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log256(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log256(value); return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << (result << 3) < value); } } /** * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers. */ function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) { return uint8(rounding) % 2 == 1; } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SignedMath.sol) pragma solidity ^0.8.20; import {SafeCast} from "./SafeCast.sol"; /** * @dev Standard signed math utilities missing in the Solidity language. */ library SignedMath { /** * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant. * * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone. * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute * one branch when needed, making this function more expensive. */ function ternary(bool condition, int256 a, int256 b) internal pure returns (int256) { unchecked { // branchless ternary works because: // b ^ (a ^ b) == a // b ^ 0 == b return b ^ ((a ^ b) * int256(SafeCast.toUint(condition))); } } /** * @dev Returns the largest of two signed numbers. */ function max(int256 a, int256 b) internal pure returns (int256) { return ternary(a > b, a, b); } /** * @dev Returns the smallest of two signed numbers. */ function min(int256 a, int256 b) internal pure returns (int256) { return ternary(a < b, a, b); } /** * @dev Returns the average of two signed numbers without overflow. * The result is rounded towards zero. */ function average(int256 a, int256 b) internal pure returns (int256) { // Formula from the book "Hacker's Delight" int256 x = (a & b) + ((a ^ b) >> 1); return x + (int256(uint256(x) >> 255) & (a ^ b)); } /** * @dev Returns the absolute unsigned value of a signed value. */ function abs(int256 n) internal pure returns (uint256) { unchecked { // Formula from the "Bit Twiddling Hacks" by Sean Eron Anderson. // Since `n` is a signed integer, the generated bytecode will use the SAR opcode to perform the right shift, // taking advantage of the most significant (or "sign" bit) in two's complement representation. // This opcode adds new most significant bits set to the value of the previous most significant bit. As a result, // the mask will either be `bytes(0)` (if n is positive) or `~bytes32(0)` (if n is negative). int256 mask = n >> 255; // A `bytes(0)` mask leaves the input unchanged, while a `~bytes32(0)` mask complements it. return uint256((n + mask) ^ mask); } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; /** * @dev Helper library for emitting standardized panic codes. * * ```solidity * contract Example { * using Panic for uint256; * * // Use any of the declared internal constants * function foo() { Panic.GENERIC.panic(); } * * // Alternatively * function foo() { Panic.panic(Panic.GENERIC); } * } * ``` * * Follows the list from https://github.com/ethereum/solidity/blob/v0.8.24/libsolutil/ErrorCodes.h[libsolutil]. */ // slither-disable-next-line unused-state library Panic { /// @dev generic / unspecified error uint256 internal constant GENERIC = 0x00; /// @dev used by the assert() builtin uint256 internal constant ASSERT = 0x01; /// @dev arithmetic underflow or overflow uint256 internal constant UNDER_OVERFLOW = 0x11; /// @dev division or modulo by zero uint256 internal constant DIVISION_BY_ZERO = 0x12; /// @dev enum conversion error uint256 internal constant ENUM_CONVERSION_ERROR = 0x21; /// @dev invalid encoding in storage uint256 internal constant STORAGE_ENCODING_ERROR = 0x22; /// @dev empty array pop uint256 internal constant EMPTY_ARRAY_POP = 0x31; /// @dev array out of bounds access uint256 internal constant ARRAY_OUT_OF_BOUNDS = 0x32; /// @dev resource error (too large allocation or too large array) uint256 internal constant RESOURCE_ERROR = 0x41; /// @dev calling invalid internal function uint256 internal constant INVALID_INTERNAL_FUNCTION = 0x51; /// @dev Reverts with a panic code. Recommended to use with /// the internal constants with predefined codes. function panic(uint256 code) internal pure { /// @solidity memory-safe-assembly assembly { mstore(0x00, 0x4e487b71) mstore(0x20, code) revert(0x1c, 0x24) } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SafeCast.sol) // This file was procedurally generated from scripts/generate/templates/SafeCast.js. pragma solidity ^0.8.20; /** * @dev Wrappers over Solidity's uintXX/intXX/bool casting operators with added overflow * checks. * * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can * easily result in undesired exploitation or bugs, since developers usually * assume that overflows raise errors. `SafeCast` restores this intuition by * reverting the transaction when such an operation overflows. * * Using this library instead of the unchecked operations eliminates an entire * class of bugs, so it's recommended to use it always. */ library SafeCast { /** * @dev Value doesn't fit in an uint of `bits` size. */ error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value); /** * @dev An int value doesn't fit in an uint of `bits` size. */ error SafeCastOverflowedIntToUint(int256 value); /** * @dev Value doesn't fit in an int of `bits` size. */ error SafeCastOverflowedIntDowncast(uint8 bits, int256 value); /** * @dev An uint value doesn't fit in an int of `bits` size. */ error SafeCastOverflowedUintToInt(uint256 value); /** * @dev Returns the downcasted uint248 from uint256, reverting on * overflow (when the input is greater than largest uint248). * * Counterpart to Solidity's `uint248` operator. * * Requirements: * * - input must fit into 248 bits */ function toUint248(uint256 value) internal pure returns (uint248) { if (value > type(uint248).max) { revert SafeCastOverflowedUintDowncast(248, value); } return uint248(value); } /** * @dev Returns the downcasted uint240 from uint256, reverting on * overflow (when the input is greater than largest uint240). * * Counterpart to Solidity's `uint240` operator. * * Requirements: * * - input must fit into 240 bits */ function toUint240(uint256 value) internal pure returns (uint240) { if (value > type(uint240).max) { revert SafeCastOverflowedUintDowncast(240, value); } return uint240(value); } /** * @dev Returns the downcasted uint232 from uint256, reverting on * overflow (when the input is greater than largest uint232). * * Counterpart to Solidity's `uint232` operator. * * Requirements: * * - input must fit into 232 bits */ function toUint232(uint256 value) internal pure returns (uint232) { if (value > type(uint232).max) { revert SafeCastOverflowedUintDowncast(232, value); } return uint232(value); } /** * @dev Returns the downcasted uint224 from uint256, reverting on * overflow (when the input is greater than largest uint224). * * Counterpart to Solidity's `uint224` operator. * * Requirements: * * - input must fit into 224 bits */ function toUint224(uint256 value) internal pure returns (uint224) { if (value > type(uint224).max) { revert SafeCastOverflowedUintDowncast(224, value); } return uint224(value); } /** * @dev Returns the downcasted uint216 from uint256, reverting on * overflow (when the input is greater than largest uint216). * * Counterpart to Solidity's `uint216` operator. * * Requirements: * * - input must fit into 216 bits */ function toUint216(uint256 value) internal pure returns (uint216) { if (value > type(uint216).max) { revert SafeCastOverflowedUintDowncast(216, value); } return uint216(value); } /** * @dev Returns the downcasted uint208 from uint256, reverting on * overflow (when the input is greater than largest uint208). * * Counterpart to Solidity's `uint208` operator. * * Requirements: * * - input must fit into 208 bits */ function toUint208(uint256 value) internal pure returns (uint208) { if (value > type(uint208).max) { revert SafeCastOverflowedUintDowncast(208, value); } return uint208(value); } /** * @dev Returns the downcasted uint200 from uint256, reverting on * overflow (when the input is greater than largest uint200). * * Counterpart to Solidity's `uint200` operator. * * Requirements: * * - input must fit into 200 bits */ function toUint200(uint256 value) internal pure returns (uint200) { if (value > type(uint200).max) { revert SafeCastOverflowedUintDowncast(200, value); } return uint200(value); } /** * @dev Returns the downcasted uint192 from uint256, reverting on * overflow (when the input is greater than largest uint192). * * Counterpart to Solidity's `uint192` operator. * * Requirements: * * - input must fit into 192 bits */ function toUint192(uint256 value) internal pure returns (uint192) { if (value > type(uint192).max) { revert SafeCastOverflowedUintDowncast(192, value); } return uint192(value); } /** * @dev Returns the downcasted uint184 from uint256, reverting on * overflow (when the input is greater than largest uint184). * * Counterpart to Solidity's `uint184` operator. * * Requirements: * * - input must fit into 184 bits */ function toUint184(uint256 value) internal pure returns (uint184) { if (value > type(uint184).max) { revert SafeCastOverflowedUintDowncast(184, value); } return uint184(value); } /** * @dev Returns the downcasted uint176 from uint256, reverting on * overflow (when the input is greater than largest uint176). * * Counterpart to Solidity's `uint176` operator. * * Requirements: * * - input must fit into 176 bits */ function toUint176(uint256 value) internal pure returns (uint176) { if (value > type(uint176).max) { revert SafeCastOverflowedUintDowncast(176, value); } return uint176(value); } /** * @dev Returns the downcasted uint168 from uint256, reverting on * overflow (when the input is greater than largest uint168). * * Counterpart to Solidity's `uint168` operator. * * Requirements: * * - input must fit into 168 bits */ function toUint168(uint256 value) internal pure returns (uint168) { if (value > type(uint168).max) { revert SafeCastOverflowedUintDowncast(168, value); } return uint168(value); } /** * @dev Returns the downcasted uint160 from uint256, reverting on * overflow (when the input is greater than largest uint160). * * Counterpart to Solidity's `uint160` operator. * * Requirements: * * - input must fit into 160 bits */ function toUint160(uint256 value) internal pure returns (uint160) { if (value > type(uint160).max) { revert SafeCastOverflowedUintDowncast(160, value); } return uint160(value); } /** * @dev Returns the downcasted uint152 from uint256, reverting on * overflow (when the input is greater than largest uint152). * * Counterpart to Solidity's `uint152` operator. * * Requirements: * * - input must fit into 152 bits */ function toUint152(uint256 value) internal pure returns (uint152) { if (value > type(uint152).max) { revert SafeCastOverflowedUintDowncast(152, value); } return uint152(value); } /** * @dev Returns the downcasted uint144 from uint256, reverting on * overflow (when the input is greater than largest uint144). * * Counterpart to Solidity's `uint144` operator. * * Requirements: * * - input must fit into 144 bits */ function toUint144(uint256 value) internal pure returns (uint144) { if (value > type(uint144).max) { revert SafeCastOverflowedUintDowncast(144, value); } return uint144(value); } /** * @dev Returns the downcasted uint136 from uint256, reverting on * overflow (when the input is greater than largest uint136). * * Counterpart to Solidity's `uint136` operator. * * Requirements: * * - input must fit into 136 bits */ function toUint136(uint256 value) internal pure returns (uint136) { if (value > type(uint136).max) { revert SafeCastOverflowedUintDowncast(136, value); } return uint136(value); } /** * @dev Returns the downcasted uint128 from uint256, reverting on * overflow (when the input is greater than largest uint128). * * Counterpart to Solidity's `uint128` operator. * * Requirements: * * - input must fit into 128 bits */ function toUint128(uint256 value) internal pure returns (uint128) { if (value > type(uint128).max) { revert SafeCastOverflowedUintDowncast(128, value); } return uint128(value); } /** * @dev Returns the downcasted uint120 from uint256, reverting on * overflow (when the input is greater than largest uint120). * * Counterpart to Solidity's `uint120` operator. * * Requirements: * * - input must fit into 120 bits */ function toUint120(uint256 value) internal pure returns (uint120) { if (value > type(uint120).max) { revert SafeCastOverflowedUintDowncast(120, value); } return uint120(value); } /** * @dev Returns the downcasted uint112 from uint256, reverting on * overflow (when the input is greater than largest uint112). * * Counterpart to Solidity's `uint112` operator. * * Requirements: * * - input must fit into 112 bits */ function toUint112(uint256 value) internal pure returns (uint112) { if (value > type(uint112).max) { revert SafeCastOverflowedUintDowncast(112, value); } return uint112(value); } /** * @dev Returns the downcasted uint104 from uint256, reverting on * overflow (when the input is greater than largest uint104). * * Counterpart to Solidity's `uint104` operator. * * Requirements: * * - input must fit into 104 bits */ function toUint104(uint256 value) internal pure returns (uint104) { if (value > type(uint104).max) { revert SafeCastOverflowedUintDowncast(104, value); } return uint104(value); } /** * @dev Returns the downcasted uint96 from uint256, reverting on * overflow (when the input is greater than largest uint96). * * Counterpart to Solidity's `uint96` operator. * * Requirements: * * - input must fit into 96 bits */ function toUint96(uint256 value) internal pure returns (uint96) { if (value > type(uint96).max) { revert SafeCastOverflowedUintDowncast(96, value); } return uint96(value); } /** * @dev Returns the downcasted uint88 from uint256, reverting on * overflow (when the input is greater than largest uint88). * * Counterpart to Solidity's `uint88` operator. * * Requirements: * * - input must fit into 88 bits */ function toUint88(uint256 value) internal pure returns (uint88) { if (value > type(uint88).max) { revert SafeCastOverflowedUintDowncast(88, value); } return uint88(value); } /** * @dev Returns the downcasted uint80 from uint256, reverting on * overflow (when the input is greater than largest uint80). * * Counterpart to Solidity's `uint80` operator. * * Requirements: * * - input must fit into 80 bits */ function toUint80(uint256 value) internal pure returns (uint80) { if (value > type(uint80).max) { revert SafeCastOverflowedUintDowncast(80, value); } return uint80(value); } /** * @dev Returns the downcasted uint72 from uint256, reverting on * overflow (when the input is greater than largest uint72). * * Counterpart to Solidity's `uint72` operator. * * Requirements: * * - input must fit into 72 bits */ function toUint72(uint256 value) internal pure returns (uint72) { if (value > type(uint72).max) { revert SafeCastOverflowedUintDowncast(72, value); } return uint72(value); } /** * @dev Returns the downcasted uint64 from uint256, reverting on * overflow (when the input is greater than largest uint64). * * Counterpart to Solidity's `uint64` operator. * * Requirements: * * - input must fit into 64 bits */ function toUint64(uint256 value) internal pure returns (uint64) { if (value > type(uint64).max) { revert SafeCastOverflowedUintDowncast(64, value); } return uint64(value); } /** * @dev Returns the downcasted uint56 from uint256, reverting on * overflow (when the input is greater than largest uint56). * * Counterpart to Solidity's `uint56` operator. * * Requirements: * * - input must fit into 56 bits */ function toUint56(uint256 value) internal pure returns (uint56) { if (value > type(uint56).max) { revert SafeCastOverflowedUintDowncast(56, value); } return uint56(value); } /** * @dev Returns the downcasted uint48 from uint256, reverting on * overflow (when the input is greater than largest uint48). * * Counterpart to Solidity's `uint48` operator. * * Requirements: * * - input must fit into 48 bits */ function toUint48(uint256 value) internal pure returns (uint48) { if (value > type(uint48).max) { revert SafeCastOverflowedUintDowncast(48, value); } return uint48(value); } /** * @dev Returns the downcasted uint40 from uint256, reverting on * overflow (when the input is greater than largest uint40). * * Counterpart to Solidity's `uint40` operator. * * Requirements: * * - input must fit into 40 bits */ function toUint40(uint256 value) internal pure returns (uint40) { if (value > type(uint40).max) { revert SafeCastOverflowedUintDowncast(40, value); } return uint40(value); } /** * @dev Returns the downcasted uint32 from uint256, reverting on * overflow (when the input is greater than largest uint32). * * Counterpart to Solidity's `uint32` operator. * * Requirements: * * - input must fit into 32 bits */ function toUint32(uint256 value) internal pure returns (uint32) { if (value > type(uint32).max) { revert SafeCastOverflowedUintDowncast(32, value); } return uint32(value); } /** * @dev Returns the downcasted uint24 from uint256, reverting on * overflow (when the input is greater than largest uint24). * * Counterpart to Solidity's `uint24` operator. * * Requirements: * * - input must fit into 24 bits */ function toUint24(uint256 value) internal pure returns (uint24) { if (value > type(uint24).max) { revert SafeCastOverflowedUintDowncast(24, value); } return uint24(value); } /** * @dev Returns the downcasted uint16 from uint256, reverting on * overflow (when the input is greater than largest uint16). * * Counterpart to Solidity's `uint16` operator. * * Requirements: * * - input must fit into 16 bits */ function toUint16(uint256 value) internal pure returns (uint16) { if (value > type(uint16).max) { revert SafeCastOverflowedUintDowncast(16, value); } return uint16(value); } /** * @dev Returns the downcasted uint8 from uint256, reverting on * overflow (when the input is greater than largest uint8). * * Counterpart to Solidity's `uint8` operator. * * Requirements: * * - input must fit into 8 bits */ function toUint8(uint256 value) internal pure returns (uint8) { if (value > type(uint8).max) { revert SafeCastOverflowedUintDowncast(8, value); } return uint8(value); } /** * @dev Converts a signed int256 into an unsigned uint256. * * Requirements: * * - input must be greater than or equal to 0. */ function toUint256(int256 value) internal pure returns (uint256) { if (value < 0) { revert SafeCastOverflowedIntToUint(value); } return uint256(value); } /** * @dev Returns the downcasted int248 from int256, reverting on * overflow (when the input is less than smallest int248 or * greater than largest int248). * * Counterpart to Solidity's `int248` operator. * * Requirements: * * - input must fit into 248 bits */ function toInt248(int256 value) internal pure returns (int248 downcasted) { downcasted = int248(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(248, value); } } /** * @dev Returns the downcasted int240 from int256, reverting on * overflow (when the input is less than smallest int240 or * greater than largest int240). * * Counterpart to Solidity's `int240` operator. * * Requirements: * * - input must fit into 240 bits */ function toInt240(int256 value) internal pure returns (int240 downcasted) { downcasted = int240(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(240, value); } } /** * @dev Returns the downcasted int232 from int256, reverting on * overflow (when the input is less than smallest int232 or * greater than largest int232). * * Counterpart to Solidity's `int232` operator. * * Requirements: * * - input must fit into 232 bits */ function toInt232(int256 value) internal pure returns (int232 downcasted) { downcasted = int232(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(232, value); } } /** * @dev Returns the downcasted int224 from int256, reverting on * overflow (when the input is less than smallest int224 or * greater than largest int224). * * Counterpart to Solidity's `int224` operator. * * Requirements: * * - input must fit into 224 bits */ function toInt224(int256 value) internal pure returns (int224 downcasted) { downcasted = int224(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(224, value); } } /** * @dev Returns the downcasted int216 from int256, reverting on * overflow (when the input is less than smallest int216 or * greater than largest int216). * * Counterpart to Solidity's `int216` operator. * * Requirements: * * - input must fit into 216 bits */ function toInt216(int256 value) internal pure returns (int216 downcasted) { downcasted = int216(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(216, value); } } /** * @dev Returns the downcasted int208 from int256, reverting on * overflow (when the input is less than smallest int208 or * greater than largest int208). * * Counterpart to Solidity's `int208` operator. * * Requirements: * * - input must fit into 208 bits */ function toInt208(int256 value) internal pure returns (int208 downcasted) { downcasted = int208(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(208, value); } } /** * @dev Returns the downcasted int200 from int256, reverting on * overflow (when the input is less than smallest int200 or * greater than largest int200). * * Counterpart to Solidity's `int200` operator. * * Requirements: * * - input must fit into 200 bits */ function toInt200(int256 value) internal pure returns (int200 downcasted) { downcasted = int200(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(200, value); } } /** * @dev Returns the downcasted int192 from int256, reverting on * overflow (when the input is less than smallest int192 or * greater than largest int192). * * Counterpart to Solidity's `int192` operator. * * Requirements: * * - input must fit into 192 bits */ function toInt192(int256 value) internal pure returns (int192 downcasted) { downcasted = int192(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(192, value); } } /** * @dev Returns the downcasted int184 from int256, reverting on * overflow (when the input is less than smallest int184 or * greater than largest int184). * * Counterpart to Solidity's `int184` operator. * * Requirements: * * - input must fit into 184 bits */ function toInt184(int256 value) internal pure returns (int184 downcasted) { downcasted = int184(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(184, value); } } /** * @dev Returns the downcasted int176 from int256, reverting on * overflow (when the input is less than smallest int176 or * greater than largest int176). * * Counterpart to Solidity's `int176` operator. * * Requirements: * * - input must fit into 176 bits */ function toInt176(int256 value) internal pure returns (int176 downcasted) { downcasted = int176(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(176, value); } } /** * @dev Returns the downcasted int168 from int256, reverting on * overflow (when the input is less than smallest int168 or * greater than largest int168). * * Counterpart to Solidity's `int168` operator. * * Requirements: * * - input must fit into 168 bits */ function toInt168(int256 value) internal pure returns (int168 downcasted) { downcasted = int168(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(168, value); } } /** * @dev Returns the downcasted int160 from int256, reverting on * overflow (when the input is less than smallest int160 or * greater than largest int160). * * Counterpart to Solidity's `int160` operator. * * Requirements: * * - input must fit into 160 bits */ function toInt160(int256 value) internal pure returns (int160 downcasted) { downcasted = int160(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(160, value); } } /** * @dev Returns the downcasted int152 from int256, reverting on * overflow (when the input is less than smallest int152 or * greater than largest int152). * * Counterpart to Solidity's `int152` operator. * * Requirements: * * - input must fit into 152 bits */ function toInt152(int256 value) internal pure returns (int152 downcasted) { downcasted = int152(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(152, value); } } /** * @dev Returns the downcasted int144 from int256, reverting on * overflow (when the input is less than smallest int144 or * greater than largest int144). * * Counterpart to Solidity's `int144` operator. * * Requirements: * * - input must fit into 144 bits */ function toInt144(int256 value) internal pure returns (int144 downcasted) { downcasted = int144(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(144, value); } } /** * @dev Returns the downcasted int136 from int256, reverting on * overflow (when the input is less than smallest int136 or * greater than largest int136). * * Counterpart to Solidity's `int136` operator. * * Requirements: * * - input must fit into 136 bits */ function toInt136(int256 value) internal pure returns (int136 downcasted) { downcasted = int136(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(136, value); } } /** * @dev Returns the downcasted int128 from int256, reverting on * overflow (when the input is less than smallest int128 or * greater than largest int128). * * Counterpart to Solidity's `int128` operator. * * Requirements: * * - input must fit into 128 bits */ function toInt128(int256 value) internal pure returns (int128 downcasted) { downcasted = int128(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(128, value); } } /** * @dev Returns the downcasted int120 from int256, reverting on * overflow (when the input is less than smallest int120 or * greater than largest int120). * * Counterpart to Solidity's `int120` operator. * * Requirements: * * - input must fit into 120 bits */ function toInt120(int256 value) internal pure returns (int120 downcasted) { downcasted = int120(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(120, value); } } /** * @dev Returns the downcasted int112 from int256, reverting on * overflow (when the input is less than smallest int112 or * greater than largest int112). * * Counterpart to Solidity's `int112` operator. * * Requirements: * * - input must fit into 112 bits */ function toInt112(int256 value) internal pure returns (int112 downcasted) { downcasted = int112(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(112, value); } } /** * @dev Returns the downcasted int104 from int256, reverting on * overflow (when the input is less than smallest int104 or * greater than largest int104). * * Counterpart to Solidity's `int104` operator. * * Requirements: * * - input must fit into 104 bits */ function toInt104(int256 value) internal pure returns (int104 downcasted) { downcasted = int104(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(104, value); } } /** * @dev Returns the downcasted int96 from int256, reverting on * overflow (when the input is less than smallest int96 or * greater than largest int96). * * Counterpart to Solidity's `int96` operator. * * Requirements: * * - input must fit into 96 bits */ function toInt96(int256 value) internal pure returns (int96 downcasted) { downcasted = int96(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(96, value); } } /** * @dev Returns the downcasted int88 from int256, reverting on * overflow (when the input is less than smallest int88 or * greater than largest int88). * * Counterpart to Solidity's `int88` operator. * * Requirements: * * - input must fit into 88 bits */ function toInt88(int256 value) internal pure returns (int88 downcasted) { downcasted = int88(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(88, value); } } /** * @dev Returns the downcasted int80 from int256, reverting on * overflow (when the input is less than smallest int80 or * greater than largest int80). * * Counterpart to Solidity's `int80` operator. * * Requirements: * * - input must fit into 80 bits */ function toInt80(int256 value) internal pure returns (int80 downcasted) { downcasted = int80(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(80, value); } } /** * @dev Returns the downcasted int72 from int256, reverting on * overflow (when the input is less than smallest int72 or * greater than largest int72). * * Counterpart to Solidity's `int72` operator. * * Requirements: * * - input must fit into 72 bits */ function toInt72(int256 value) internal pure returns (int72 downcasted) { downcasted = int72(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(72, value); } } /** * @dev Returns the downcasted int64 from int256, reverting on * overflow (when the input is less than smallest int64 or * greater than largest int64). * * Counterpart to Solidity's `int64` operator. * * Requirements: * * - input must fit into 64 bits */ function toInt64(int256 value) internal pure returns (int64 downcasted) { downcasted = int64(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(64, value); } } /** * @dev Returns the downcasted int56 from int256, reverting on * overflow (when the input is less than smallest int56 or * greater than largest int56). * * Counterpart to Solidity's `int56` operator. * * Requirements: * * - input must fit into 56 bits */ function toInt56(int256 value) internal pure returns (int56 downcasted) { downcasted = int56(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(56, value); } } /** * @dev Returns the downcasted int48 from int256, reverting on * overflow (when the input is less than smallest int48 or * greater than largest int48). * * Counterpart to Solidity's `int48` operator. * * Requirements: * * - input must fit into 48 bits */ function toInt48(int256 value) internal pure returns (int48 downcasted) { downcasted = int48(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(48, value); } } /** * @dev Returns the downcasted int40 from int256, reverting on * overflow (when the input is less than smallest int40 or * greater than largest int40). * * Counterpart to Solidity's `int40` operator. * * Requirements: * * - input must fit into 40 bits */ function toInt40(int256 value) internal pure returns (int40 downcasted) { downcasted = int40(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(40, value); } } /** * @dev Returns the downcasted int32 from int256, reverting on * overflow (when the input is less than smallest int32 or * greater than largest int32). * * Counterpart to Solidity's `int32` operator. * * Requirements: * * - input must fit into 32 bits */ function toInt32(int256 value) internal pure returns (int32 downcasted) { downcasted = int32(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(32, value); } } /** * @dev Returns the downcasted int24 from int256, reverting on * overflow (when the input is less than smallest int24 or * greater than largest int24). * * Counterpart to Solidity's `int24` operator. * * Requirements: * * - input must fit into 24 bits */ function toInt24(int256 value) internal pure returns (int24 downcasted) { downcasted = int24(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(24, value); } } /** * @dev Returns the downcasted int16 from int256, reverting on * overflow (when the input is less than smallest int16 or * greater than largest int16). * * Counterpart to Solidity's `int16` operator. * * Requirements: * * - input must fit into 16 bits */ function toInt16(int256 value) internal pure returns (int16 downcasted) { downcasted = int16(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(16, value); } } /** * @dev Returns the downcasted int8 from int256, reverting on * overflow (when the input is less than smallest int8 or * greater than largest int8). * * Counterpart to Solidity's `int8` operator. * * Requirements: * * - input must fit into 8 bits */ function toInt8(int256 value) internal pure returns (int8 downcasted) { downcasted = int8(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(8, value); } } /** * @dev Converts an unsigned uint256 into a signed int256. * * Requirements: * * - input must be less than or equal to maxInt256. */ function toInt256(uint256 value) internal pure returns (int256) { // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive if (value > uint256(type(int256).max)) { revert SafeCastOverflowedUintToInt(value); } return int256(value); } /** * @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump. */ function toUint(bool b) internal pure returns (uint256 u) { /// @solidity memory-safe-assembly assembly { u := iszero(iszero(b)) } } }
{ "remappings": [ "@forge-std/=lib/forge-std/src/", "@openzeppelin/=lib/openzeppelin-contracts/contracts/", "@solady/=lib/solady/src/", "@solmate/=lib/solmate/src/", "@zora/=lib/v3/contracts/", "@chainlink/=lib/chainlink/contracts/src/v0.8/", "@delegate/=lib/delegate-registry/src/", "@seaport/=lib/seaport/lib/", "@aave/=lib/aave/src/contracts/", "@florida-v2/=lib/florida-contracts-v2/", "@permit2/=lib/permit2/src/", "test/=test/", "@const/=src/const/default/", "@manifoldxyz/=lib/v3/node_modules/@manifoldxyz/", "@openzeppelin/contracts-upgradeable/=lib/aave/lib/solidity-utils/lib/openzeppelin-contracts-upgradeable/contracts/", "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/", "@rari-capital/=lib/v3/node_modules/@rari-capital/", "@rari-capital/solmate/=lib/seaport/lib/solmate/", "aave/=lib/aave/", "chainlink/=lib/chainlink/contracts/", "delegate-registry/=lib/delegate-registry/", "ds-test/=lib/seaport/lib/ds-test/src/", "erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/", "florida-contracts-v2/=lib/florida-contracts-v2/src/", "forge-gas-snapshot/=lib/permit2/lib/forge-gas-snapshot/src/", "forge-std/=lib/forge-std/src/", "murky/=lib/seaport/lib/murky/src/", "openzeppelin-contracts-upgradeable/=lib/aave/lib/solidity-utils/lib/openzeppelin-contracts-upgradeable/", "openzeppelin-contracts/=lib/openzeppelin-contracts/", "openzeppelin/=lib/delegate-registry/lib/openzeppelin-contracts/contracts/", "permit2/=lib/permit2/", "seaport-core/=lib/seaport/lib/seaport-core/", "seaport-sol/=lib/seaport/lib/seaport-sol/", "seaport-types/=lib/seaport/lib/seaport-types/", "seaport/=lib/seaport/", "solady/=lib/solady/src/", "solarray/=lib/seaport/lib/solarray/src/", "solidity-utils/=lib/aave/lib/solidity-utils/", "solmate/=lib/solmate/src/", "v3/=lib/v3/contracts/" ], "optimizer": { "enabled": true, "runs": 20 }, "metadata": { "useLiteralContent": false, "bytecodeHash": "ipfs", "appendCBOR": true }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } }, "evmVersion": "cancun", "viaIR": true, "libraries": {} }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"address","name":"multiSourceLoanAddress","type":"address"},{"internalType":"address","name":"marketplaceContracts","type":"address"},{"internalType":"address payable","name":"wethAddress","type":"address"},{"internalType":"address payable","name":"punkMarketAddress","type":"address"},{"internalType":"address payable","name":"wrappedPunkAddress","type":"address"},{"internalType":"address payable","name":"c721Address","type":"address"},{"internalType":"address payable","name":"uniswapRouterAddress","type":"address"},{"internalType":"address payable","name":"permit2Address","type":"address"},{"internalType":"address","name":"currencyManager","type":"address"},{"components":[{"internalType":"uint128","name":"buyTax","type":"uint128"},{"internalType":"uint128","name":"sellTax","type":"uint128"}],"internalType":"struct IPurchaseBundler.Taxes","name":"taxes","type":"tuple"},{"internalType":"uint256","name":"minWaitTime","type":"uint256"},{"components":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"fraction","type":"uint256"}],"internalType":"struct WithProtocolFee.ProtocolFee","name":"protocolFee","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AddressZeroError","type":"error"},{"inputs":[],"name":"CouldNotReturnEthError","type":"error"},{"inputs":[],"name":"CurrencyNotWhitelisted","type":"error"},{"inputs":[],"name":"ECDSAInvalidSignature","type":"error"},{"inputs":[{"internalType":"uint256","name":"length","type":"uint256"}],"name":"ECDSAInvalidSignatureLength","type":"error"},{"inputs":[{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"ECDSAInvalidSignatureS","type":"error"},{"inputs":[],"name":"InvalidAddressUpdateError","type":"error"},{"inputs":[],"name":"InvalidCallbackError","type":"error"},{"inputs":[],"name":"InvalidCollateralError","type":"error"},{"inputs":[],"name":"InvalidExecutionData","type":"error"},{"inputs":[],"name":"InvalidInputError","type":"error"},{"inputs":[],"name":"InvalidPrincipalError","type":"error"},{"inputs":[],"name":"InvalidSignature","type":"error"},{"inputs":[],"name":"InvalidTaker","type":"error"},{"inputs":[{"components":[{"internalType":"uint128","name":"buyTax","type":"uint128"},{"internalType":"uint128","name":"sellTax","type":"uint128"}],"internalType":"struct IPurchaseBundler.Taxes","name":"newTaxes","type":"tuple"}],"name":"InvalidTaxesError","type":"error"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"currentMinNonce","type":"uint256"}],"name":"LowNonceError","type":"error"},{"inputs":[],"name":"MarketplaceAddressNotWhitelisted","type":"error"},{"inputs":[{"internalType":"uint256","name":"i","type":"uint256"},{"internalType":"bytes","name":"returndata","type":"bytes"}],"name":"MulticallFailed","type":"error"},{"inputs":[],"name":"OnlyLoanCallableError","type":"error"},{"inputs":[],"name":"OnlyWethSupportedError","type":"error"},{"inputs":[],"name":"OrderCancelled","type":"error"},{"inputs":[],"name":"OrderExpired","type":"error"},{"inputs":[{"internalType":"uint256","name":"_pendingProtocolFeeSetTime","type":"uint256"}],"name":"TooEarlyError","type":"error"},{"inputs":[],"name":"TooSoonError","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"nonce","type":"uint256"}],"name":"AllOrdersCancelled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256[]","name":"loanIds","type":"uint256[]"}],"name":"BNPLLoansStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newAddress","type":"address"}],"name":"MultiSourceLoanPendingUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newAddress","type":"address"}],"name":"MultiSourceLoanUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"maker","type":"address"},{"indexed":true,"internalType":"address","name":"taker","type":"address"},{"indexed":true,"internalType":"address","name":"collection","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"address","name":"currency","type":"address"},{"indexed":false,"internalType":"uint256","name":"price","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"nonce","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"expiration","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isAsk","type":"bool"}],"name":"OrderExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"components":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"fraction","type":"uint256"}],"indexed":false,"internalType":"struct WithProtocolFee.ProtocolFee","name":"fee","type":"tuple"}],"name":"ProtocolFeePendingUpdate","type":"event"},{"anonymous":false,"inputs":[{"components":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"fraction","type":"uint256"}],"indexed":false,"internalType":"struct WithProtocolFee.ProtocolFee","name":"fee","type":"tuple"}],"name":"ProtocolFeeUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256[]","name":"loanIds","type":"uint256[]"}],"name":"SellAndRepayExecuted","type":"event"},{"anonymous":false,"inputs":[{"components":[{"internalType":"uint128","name":"buyTax","type":"uint128"},{"internalType":"uint128","name":"sellTax","type":"uint128"}],"indexed":false,"internalType":"struct IPurchaseBundler.Taxes","name":"newTaxes","type":"tuple"}],"name":"TaxesPendingUpdate","type":"event"},{"anonymous":false,"inputs":[{"components":[{"internalType":"uint128","name":"buyTax","type":"uint128"},{"internalType":"uint128","name":"sellTax","type":"uint128"}],"indexed":false,"internalType":"struct IPurchaseBundler.Taxes","name":"taxes","type":"tuple"}],"name":"TaxesUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newOwner","type":"address"}],"name":"TransferOwnerRequested","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FEE_UPDATE_NOTICE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INITIAL_DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_WAIT_TIME","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TAX_UPDATE_NOTICE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VERSION","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"borrower","type":"address"},{"internalType":"uint256","name":"nftCollateralTokenId","type":"uint256"},{"internalType":"address","name":"nftCollateralAddress","type":"address"},{"internalType":"address","name":"principalAddress","type":"address"},{"internalType":"uint256","name":"principalAmount","type":"uint256"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"},{"components":[{"internalType":"uint256","name":"loanId","type":"uint256"},{"internalType":"uint256","name":"floor","type":"uint256"},{"internalType":"uint256","name":"principalAmount","type":"uint256"},{"internalType":"address","name":"lender","type":"address"},{"internalType":"uint256","name":"accruedInterest","type":"uint256"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"aprBps","type":"uint256"}],"internalType":"struct IMultiSourceLoan.Tranche[]","name":"tranche","type":"tuple[]"},{"internalType":"uint256","name":"protocolFee","type":"uint256"}],"internalType":"struct IMultiSourceLoan.Loan","name":"_loan","type":"tuple"},{"internalType":"bytes","name":"_executionData","type":"bytes"}],"name":"afterNFTTransfer","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"borrower","type":"address"},{"internalType":"uint256","name":"nftCollateralTokenId","type":"uint256"},{"internalType":"address","name":"nftCollateralAddress","type":"address"},{"internalType":"address","name":"principalAddress","type":"address"},{"internalType":"uint256","name":"principalAmount","type":"uint256"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"},{"components":[{"internalType":"uint256","name":"loanId","type":"uint256"},{"internalType":"address","name":"lender","type":"address"},{"internalType":"uint256","name":"principalAmount","type":"uint256"},{"internalType":"uint256","name":"accruedInterest","type":"uint256"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"aprBps","type":"uint256"}],"internalType":"struct IMultiSourceLoan.Source[]","name":"source","type":"tuple[]"}],"internalType":"struct IMultiSourceLoan.Loan","name":"_loan","type":"tuple"},{"internalType":"bytes","name":"_executionData","type":"bytes"}],"name":"afterNFTTransfer","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"borrower","type":"address"},{"internalType":"uint256","name":"nftCollateralTokenId","type":"uint256"},{"internalType":"address","name":"nftCollateralAddress","type":"address"},{"internalType":"address","name":"principalAddress","type":"address"},{"internalType":"uint256","name":"principalAmount","type":"uint256"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"},{"components":[{"internalType":"uint256","name":"loanId","type":"uint256"},{"internalType":"address","name":"lender","type":"address"},{"internalType":"uint256","name":"principalAmount","type":"uint256"},{"internalType":"uint256","name":"accruedInterest","type":"uint256"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"aprBps","type":"uint256"}],"internalType":"struct IMultiSourceLoan.Source[]","name":"source","type":"tuple[]"}],"internalType":"struct IMultiSourceLoan.Loan","name":"_loan","type":"tuple"},{"internalType":"uint256","name":"_fee","type":"uint256"},{"internalType":"bytes","name":"_executionData","type":"bytes"}],"name":"afterPrincipalTransfer","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"borrower","type":"address"},{"internalType":"uint256","name":"nftCollateralTokenId","type":"uint256"},{"internalType":"address","name":"nftCollateralAddress","type":"address"},{"internalType":"address","name":"principalAddress","type":"address"},{"internalType":"uint256","name":"principalAmount","type":"uint256"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"},{"components":[{"internalType":"uint256","name":"loanId","type":"uint256"},{"internalType":"uint256","name":"floor","type":"uint256"},{"internalType":"uint256","name":"principalAmount","type":"uint256"},{"internalType":"address","name":"lender","type":"address"},{"internalType":"uint256","name":"accruedInterest","type":"uint256"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"aprBps","type":"uint256"}],"internalType":"struct IMultiSourceLoan.Tranche[]","name":"tranche","type":"tuple[]"},{"internalType":"uint256","name":"protocolFee","type":"uint256"}],"internalType":"struct IMultiSourceLoan.Loan","name":"_loan","type":"tuple"},{"internalType":"uint256","name":"_fee","type":"uint256"},{"internalType":"bytes","name":"_executionData","type":"bytes"}],"name":"afterPrincipalTransfer","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"currency","type":"address"}],"name":"approveForSwap","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"executionData","type":"bytes[]"}],"name":"buy","outputs":[{"internalType":"uint256[]","name":"loanIds","type":"uint256[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"nonce","type":"uint256"}],"name":"cancelAllOrders","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"nonce","type":"uint256"}],"name":"cancelOrder","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"cancelled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"assets","type":"address[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"uint256[]","name":"premiums","type":"uint256[]"},{"internalType":"address","name":"","type":"address"},{"internalType":"bytes","name":"params","type":"bytes"}],"name":"executeOperation","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"address","name":"collection","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"currency","type":"address"},{"internalType":"uint256","name":"price","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"bool","name":"isAsk","type":"bool"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct ITradeMarketplace.Order","name":"order","type":"tuple"}],"name":"executeOrder","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ERC20[]","name":"currencies","type":"address[]"},{"internalType":"uint256[]","name":"currencyAmounts","type":"uint256[]"},{"internalType":"contract ERC721[]","name":"collections","type":"address[]"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"internalType":"address","name":"marketPlace","type":"address"},{"internalType":"bytes[]","name":"executionData","type":"bytes[]"}],"name":"executeSell","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"wethPrincipalSwapData","type":"bytes"},{"internalType":"contract ERC20","name":"principal","type":"address"},{"internalType":"contract ERC721","name":"collection","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"executionData","type":"bytes"}],"name":"executeSellWithETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"contract IPool","name":"pool","type":"address"},{"internalType":"address[]","name":"assets","type":"address[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"internalType":"struct IPurchaseBundler.AaveBorrowArgs","name":"borrowArgs","type":"tuple"},{"components":[{"internalType":"contract ERC20[]","name":"currencies","type":"address[]"},{"internalType":"uint256[]","name":"currencyAmounts","type":"uint256[]"},{"internalType":"contract ERC721[]","name":"collections","type":"address[]"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"internalType":"address","name":"marketPlace","type":"address"},{"internalType":"bytes[]","name":"executionData","type":"bytes[]"}],"internalType":"struct IPurchaseBundler.ExecuteSellArgs","name":"executeSellArgs","type":"tuple"},{"internalType":"bytes[]","name":"loanExecutionData","type":"bytes[]"}],"internalType":"struct IPurchaseBundler.ExecuteSellWithLoanArgs","name":"args","type":"tuple"}],"name":"executeSellWithLoan","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newAddress","type":"address"}],"name":"finalUpdateMultiSourceLoanAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getMultiSourceLoanAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getPendingProtocolFee","outputs":[{"components":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"fraction","type":"uint256"}],"internalType":"struct WithProtocolFee.ProtocolFee","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getPendingProtocolFeeSetTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getPendingTaxes","outputs":[{"components":[{"internalType":"uint128","name":"buyTax","type":"uint128"},{"internalType":"uint128","name":"sellTax","type":"uint128"}],"internalType":"struct IPurchaseBundler.Taxes","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getPendingTaxesSetTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getProtocolFee","outputs":[{"components":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"fraction","type":"uint256"}],"internalType":"struct WithProtocolFee.ProtocolFee","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTaxes","outputs":[{"components":[{"internalType":"uint128","name":"buyTax","type":"uint128"},{"internalType":"uint128","name":"sellTax","type":"uint128"}],"internalType":"struct IPurchaseBundler.Taxes","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"address","name":"collection","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"currency","type":"address"},{"internalType":"uint256","name":"price","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"bool","name":"isAsk","type":"bool"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct ITradeMarketplace.Order","name":"order","type":"tuple"}],"name":"isOrderCancelled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"minNonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"address","name":"collection","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"currency","type":"address"},{"internalType":"uint256","name":"price","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"},{"internalType":"bool","name":"isAsk","type":"bool"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct ITradeMarketplace.Order","name":"order","type":"tuple"}],"name":"orderHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwnerTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_newOwner","type":"address"}],"name":"requestTransferOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"executionData","type":"bytes[]"}],"name":"sell","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"setProtocolFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"setTaxes","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newAddress","type":"address"}],"name":"updateMultiSourceLoanAddressFirst","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"fraction","type":"uint256"}],"internalType":"struct WithProtocolFee.ProtocolFee","name":"_newProtocolFee","type":"tuple"}],"name":"updateProtocolFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint128","name":"buyTax","type":"uint128"},{"internalType":"uint128","name":"sellTax","type":"uint128"}],"internalType":"struct IPurchaseBundler.Taxes","name":"_newTaxes","type":"tuple"}],"name":"updateTaxes","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]
Deployed Bytecode
0x608080604052600436101561001a575b50361561001857005b005b5f905f3560e01c90816306fdde0314611ab157508063150b7a0214611a5b57806315f27275146119ad5780631d1432771461199557806326e726a5146118465780632973ef2d146117f05780632fd8abab1461177c578063311cf152146116f95780633644e515146116d757806344af681c146116ba5780634e9c2be71461167e57806350a03935146116435780635122afb1146115b8578063514fcac71461158057806356bf54241461155e578063628b22071461149057806367e224041461145657806373b99f10146113b5578063864019591461131b578063880ad0af1461125b5780638b661592146111a25780638da5cb5b1461117b5780638fdbcbc814611142578063903da4ed14610fc6578063920f5c841461084657806392de7a9114610771578063931c56e91461075357806394a2d3d5146106cb578063974dc93a14610627578063a0c1d3fc146105ac578063a5a410311461057c578063a7eec64814610500578063aa99fa98146104c7578063aaea91911461049e578063b874f69514610455578063bca67b931461041a578063bd545f531461038f578063bfd4968f14610371578063c4ca53ad14610353578063c7ee908b14610337578063d96d761f14610319578063e30c3978146102f0578063e69d8c47146102b4578063f2fde38b146102475763ffa1ad740361000f573461024457806003193601126102445761024061022c611f04565b604051918291602083526020830190611c84565b0390f35b80fd5b503461024457602036600319011261024457610261611ca9565b8154906001600160a01b039061027a3383851614611f21565b166001600160a01b03199190911681178255337f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08380a380f35b50346102445780600319360112610244575f60206040516102d481611bf0565b82815201526102406102e46127e4565b60405191829182611ee4565b50346102445780600319360112610244576001546040516001600160a01b039091168152602090f35b50346102445780600319360112610244576020600c54604051908152f35b50346102445761035061034936611d84565b3390614684565b80f35b50346102445780600319360112610244576020600254604051908152f35b5034610244578060031936011261024457602060405162093a808152f35b50346102445760203660031901126102445760043533825260086020526040822054818110156103f7575033825260086020528060408320556040519081527f83a782ac7424737a1190d4668474e765f07d603de0485a081dbc343ac1b0209960203392a280f35b6064925060405191621d572760e11b835233600484015260248301526044820152fd5b503461024457806003193601126102445760206040517f000000000000000000000000000000000000000000000000000000000002a3008152f35b50346102445760403660031901126102445760209060ff906040906001600160a01b03610480611ca9565b16815260098452818120602435825284522054166040519015158152f35b5034610244578060031936011261024457600f546040516001600160a01b039091168152602090f35b5034610244576020366003190112610244576020906040906001600160a01b036104ef611ca9565b168152600883522054604051908152f35b5060a0366003190112610244576001600160401b036004358181116105785761052d903690600401611ce9565b610535611cbf565b916044356001600160a01b03811681036105745760843594851161057057610564610350953690600401611ce9565b94909360643593612f68565b8580fd5b5f80fd5b8280fd5b50346102445780600319360112610244575f602060405161059c81611bf0565b82815201526102406102e4612809565b5034610244576020366003190112610244577f90d46988c3bc99875b23d7ab0c600794aa7c3eb0100dac7217e87dd8a43973d160206105e9611ca9565b83546001600160a01b0391906106029083163314611f21565b61060b81613838565b168060018060a01b0319600e541617600e55604051908152a180f35b5034610244576003196060368201126106c757600435916001600160401b03918284116106c757610120908436030112610244576044359182116102445750610674903690600401611ce9565b600f549091906001600160a01b031633036106b5576106a39261069e602435913690600401611fb3565b613c38565b604051634ba6e49d60e11b8152602090f35b6040516317f323c560e21b8152600490fd5b5080fd5b5034610244576003196040368201126106c757600435916001600160401b03918284116106c757610100908436030112610244576024359182116102445750610718903690600401611ce9565b600f549091906001600160a01b031633036106b55761073c6107419360040161397a565b6133d2565b6040516394a2d3d560e01b8152602090f35b5034610244578060031936011261024457602060405162278d008152f35b50346102445760403660031901126102445761079760018060a01b038254163314611f21565b6001600160801b0361138880826107ac612f0a565b1611908115610833575b50610815576107c3612f0a565b166001600160801b03196107d5612f20565b60801b1617600b5542600c557ff528f8d612a54ddae648d367a29ae853b179fc9e7deaf6be2282426174d9b25b6040518061080f81612f36565b0390a180f35b604051637603e5df60e01b81528061082f60048201612f36565b0390fd5b90508161083e612f20565b16115f6107b6565b50346105745760a0366003190112610574576004356001600160401b03811161057457610877903690600401611e81565b916024356001600160401b03811161057457610897903690600401611e81565b90916044356001600160401b038111610574576108b8903690600401611e81565b94906064356001600160a01b03811603610574576084356001600160401b038111610574576108eb903690600401611ce9565b60208282819894010312610574578535906001600160401b0382116105745760608288018289010312610574576040519661092588611ba6565b828101356001600160401b03811161057457606081858401018484010312610574576040519061095482611ba6565b6109618186850101611cd5565b82526020818685010101356001600160401b03811161057457848401601f8284898801010101121561057457808287860101013561099e81611f9c565b916109ac6040519384611c42565b818352602083019087870160208460051b83888d8c0101010101116105745790602082868b8a01010101915b60208460051b82888d8c01010101018310610fab57505050506020830152604081868501010135906001600160401b03821161057457610a2091858501918786010101612cf5565b6040820152885260208382010135926001600160401b0384116105745760c08482840101848401031261057457604051938460c08101106001600160401b0360c087011117610f975760c085016040528082840101356001600160401b03811161057457848401601f82848688010101011215610574578082848601010135610aa881611f9c565b91610ab66040519384611c42565b818352602083019087870160208460051b83888a8c010101010111610574579060208286888a01010101915b60208460051b82888a8c01010101018310610f7c575050505085526020818385010101356001600160401b03811161057457610b279085850190838587010101612cf5565b60208601526040818385010101356001600160401b03811161057457848401601f82848688010101011215610574578082848601010135610b6781611f9c565b91610b756040519384611c42565b818352602083019087870160208460051b83888a8c010101010111610574579060208286888a01010101915b60208460051b82888a8c01010101018310610f61575050505060408601526060818385010101356001600160401b03811161057457610be99085850190838587010101612cf5565b6060860152610bfe6080828486010101611cd5565b608086015260a081838501010135906001600160401b03821161057457610c2d91858501918486010101612d55565b60a08501526020890193845260408183010135906001600160401b03821161057457610c5d938301920101612d55565b6040870152518051906020810151916040820151606083015160a0600180821b0360808601511694015192303b1561057457949291906040519586946345b30ac960e11b865260c4860160c060048801528251809152602060e488019301905f5b818110610f3f57505050858203600319016024870152610cde9190611eb1565b600319858203016044860152602080845192838152019301905f5b818110610f1d57505050938392610d20610d37935f97600319878303016064880152611eb1565b9160848501526003198483030160a4850152612dd4565b038183305af18015610f1257610eff575b508380610d8260018060a01b03600f54166040890151604051948580948193631592ca1b60e31b8352602060048401526024830190612dd4565b03925af18015610ef457610ed2575b505f5c956001600160a01b03871615610eca575b845b898110610db957602060405160018152f35b610dda610dc782858561285c565b35610dd3838c8861285c565b3590611f5c565b906001600160a01b03610df6610df1838e8a61285c565b6127c3565b16916040516370a0823160e01b8152306004820152602081602481875afa908115610ebf578991610e8a575b50600193610e5992918280821015610e5f57610e4991610e41916127d7565b308e846138b0565b848060a01b038b515116906132b7565b01610da7565b90818111610e6f575b5050610e49565b610e8391610e7c916127d7565b8d83614467565b815f610e68565b9190506020823d602011610eb7575b81610ea660209383611c42565b810103126105745790516001610e22565b3d9150610e99565b6040513d8b823e3d90fd5b339650610da5565b610eed903d8087833e610ee58183611c42565b810190612e75565b505f610d91565b6040513d87823e3d90fd5b610f0a919450611bc1565b5f925f610d48565b6040513d5f823e3d90fd5b82516001600160a01b0316855288965060209485019490920191600101610cf9565b82516001600160a01b031685528a985060209485019490920191600101610cbe565b6020808093610f6f86611cd5565b8152019301929150610ba1565b6020808093610f8a86611cd5565b8152019301929150610ae2565b634e487b7160e01b5f52604160045260245ffd5b6020808093610fb986611cd5565b81520193019291506109d8565b602080600319360112610574576004356001600160401b03811161057457610ff2903690600401611e81565b600f54604051631592ca1b60e31b815291939291905f9082906001600160a01b03168183816110258a8960048401614035565b03925af1908115610f12575f91611128575b506110428151612427565b938151925f5b8481106110d257868647806110a4575b507f1f28de8f4bbe043cea61f4041f1b3720b457050475d0546f56a2619909695ee56040518281528061108d84820186611eb1565b0390a1610240604051928284938452830190611eb1565b5f80808093335af16110b4613bcc565b50156110c05782611058565b6040516301ca489160e41b8152600490fd5b60046110df828585614099565b90501115611116576110f18185612459565b519086828051810103126105745786600192015161110f828a612459565b5201611048565b6040516324017b7b60e21b8152600490fd5b61113c91503d805f833e610ee58183611c42565b84611037565b34610574576020366003190112610574576004356001600160401b03811161057457611175610018913690600401611e81565b9061419d565b34610574575f366003190112610574575f546040516001600160a01b039091168152602090f35b346105745760c0366003190112610574576001600160401b03600435818111610574576111d3903690600401611e81565b90602435838111610574576111ec903690600401611e81565b60443585811161057457611204903690600401611e81565b6064929192358781116105745761121f903690600401611e81565b6084359590949193906001600160a01b03871687036105745760a435998a11610574576112536100189a3690600401611e81565b99909861282e565b34610574575f366003190112610574576112986002547f000000000000000000000000000000000000000000000000000000000002a30090611f5c565b4210611309576001546001600160a01b03811633036112f7575f8054336001600160a01b03199182168117835592166001555f1960025581907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a3005b6040516319e9109760e11b8152600490fd5b604051632a28ea7760e21b8152600490fd5b3461057457602036600319011261057457611334611ca9565b5f546001600160a01b03919061134d9083163314611f21565b81600e54911680928216036113a357600f80546001600160a01b0319908116841790915516600e556040519081527fba813f9b0b1acaad83d714b0223ffd2d50d44fae13fd84e7ef19fbf2e5664a6390602090a1005b60405163b7102db560e01b8152600490fd5b34610574575f3660031901126105745760075462278d008101809111611442574210611309577fb3c1d38dbdc9199d0ce01f386d70e29014ed4af7af1d321ca6641a91f4b4dc0c61143d6114076127e4565b8051600380546001600160a01b0319166001600160a01b0392909216919091179055602081015160045560405191829182611ee4565b0390a1005b634e487b7160e01b5f52601160045260245ffd5b34610574575f3660031901126105745760206040517fcaf223f64b5e007d059f6a459f3957a575131b4b0afce2012276a2a5de7dcc9f8152f35b3461057457600319606036820112610574576001600160401b0360043581811161057457610100816004019382360301126105745760243591604435908111610574576114e1903690600401611ce9565b600f546001600160a01b0395919492939190861633036106b55761154761069e9261154c9761151d866084611515876127c3565b9401356127d7565b9130917f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2166138b0565b61397a565b60405163628b220760e01b8152602090f35b3461057457602061157661157136611d84565b61385a565b6040519015158152f35b3461057457602036600319011261057457335f52600960205260405f206004355f5260205260405f20600160ff198254161790555f80f35b34610574576040366003190112610574577f160fef22fef07b45037a807beef2c89408a81168d1055cf34024c85396b882af604060018060a01b03611601815f54163314611f21565b61161161160c6127ad565b613838565b6116196127ad565b168060018060a01b03196005541617600555602435806006554260075582519182526020820152a1005b34610574575f366003190112610574575f602060405161166281611bf0565b8281520152610240611672611f69565b60405191829182611e5e565b346105745760031960203682011261057457600435906001600160401b0382116105745760609082360301126105745761001890600401612263565b34610574575f366003190112610574576020600754604051908152f35b34610574575f3660031901126105745760206116f1612105565b604051908152f35b346105745760031960403682011261057457600435906001600160401b0390818311610574576101209083360301126105745760243590811161057457611744903690600401611ce9565b600f549091906001600160a01b031633036106b55761073c61176a933690600401611fb3565b60405163188e78a960e11b8152602090f35b34610574576020366003190112610574577f73541446fa014a1018bb483540e823dcc1bb0c55215d112b403bf614ff86672f60206117b8611ca9565b5f546001600160a01b0391906117d19083163314611f21565b168060018060a01b0319600154161760015542600255604051908152a1005b34610574575f366003190112610574575f602060405161180f81611bf0565b828152015261024060405161182381611bf0565b600d546001600160801b038116825260801c602082015260405191829182611e5e565b346105745760208060031936011261057457611860611ca9565b604051633af32abf60e01b81526001600160a01b0391821660048201819052919083816024817f0000000000000000000000004150ded32a6d3bfecae76e7558af48019034492786165afa908115610f12575f91611960575b501561194e5760445f918285946040519263095ea7b360e01b84527f000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba3166004840152811960248401525af13d15601f3d1160015f51141617161561191957005b6064906040519062461bcd60e51b82526004820152600e60248201526d1054141493d59157d1905253115160921b6044820152fd5b6040516317d818f360e21b8152600490fd5b90508381813d831161198e575b6119778183611c42565b810103126105745761198890611f8f565b846118b9565b503d61196d565b346105745760206116f16119a836611d84565b6131f6565b34610574575f366003190112610574576119d160018060a01b035f54163314611f21565b600c5462093a808101808211611442574210611a43577ff3d8abd9281bbe4d8207c88a0ba12bf59b0ca9d9e505ec4e38fe5f3b1172fb1861143d611a13611f69565b8051602082015160801b6001600160801b0319166001600160801b039190911617600d5560405191829182611e5e565b60249060405190635c269db560e01b82526004820152fd5b3461057457608036600319011261057457611a74611ca9565b50611a7d611cbf565b506064356001600160401b03811161057457611a9d903690600401611ce9565b5050604051630a85bd0160e11b8152602090f35b34610574575f36600319011261057457600a545f82611acf83611b6e565b91828252602093600190856001821691825f14611b4e575050600114611b11575b50611afd92500383611c42565b610240604051928284938452830190611c84565b849150600a5f52815f20905f915b858310611b36575050611afd935082010185611af0565b80548389018501528794508693909201918101611b1f565b60ff191685820152611afd95151560051b8501019250879150611af09050565b90600182811c92168015611b9c575b6020831014611b8857565b634e487b7160e01b5f52602260045260245ffd5b91607f1691611b7d565b606081019081106001600160401b03821117610f9757604052565b6001600160401b038111610f9757604052565b61014081019081106001600160401b03821117610f9757604052565b604081019081106001600160401b03821117610f9757604052565b61012081019081106001600160401b03821117610f9757604052565b60e081019081106001600160401b03821117610f9757604052565b90601f801991011681019081106001600160401b03821117610f9757604052565b5f5b838110611c745750505f910152565b8181015183820152602001611c65565b90602091611c9d81518092818552858086019101611c63565b601f01601f1916010190565b600435906001600160a01b038216820361057457565b602435906001600160a01b038216820361057457565b35906001600160a01b038216820361057457565b9181601f84011215610574578235916001600160401b038311610574576020838186019501011161057457565b3590811515820361057457565b6001600160401b038111610f9757601f01601f191660200190565b81601f8201121561057457803590611d5582611d23565b92611d636040519485611c42565b8284526020838301011161057457815f926020809301838601378301015290565b60031960208282011261057457600435916001600160401b0391828411610574576101409084830301126105745760405192611dbf84611bd4565b611dcb81600401611cd5565b8452611dd960248201611cd5565b6020850152611dea60448201611cd5565b604085015260648101356060850152611e0560848201611cd5565b608085015260a481013560a085015260c481013560c085015260e481013560e0850152611e356101048201611d16565b61010085015261012481013592831161057457611e559201600401611d3e565b61012082015290565b81516001600160801b039081168252602092830151169181019190915260400190565b9181601f84011215610574578235916001600160401b038311610574576020808501948460051b01011161057457565b9081518082526020808093019301915f5b828110611ed0575050505090565b835185529381019392810192600101611ec2565b81516001600160a01b031681526020918201519181019190915260400190565b60405190611f1182611bf0565b60018252603160f81b6020830152565b15611f2857565b60405162461bcd60e51b815260206004820152600c60248201526b15539055551213d49256915160a21b6044820152606490fd5b9190820180921161144257565b60405190611f7682611bf0565b600b546001600160801b038116835260801c6020830152565b5190811515820361057457565b6001600160401b038111610f975760051b60200190565b91906101208382031261057457604092835191611fcf83611c0b565b8294611fda83611cd5565b845260208084013581860152611ff1828501611cd5565b8286015260609061200460608601611cd5565b60608701526080916080860135608088015260a09360a087013560a089015260c08088013560c08a015260e09560e08901356001600160401b0381116105745789019588601f880112156105745786359461205e86611f9c565b9961206b86519b8c611c42565b868b528760e0818d0198028a010198818a11610574578801965b8988106120a857505050505050505050505060e083015261010080910135910152565b8a8883031261057457888b9188516120bf81611c27565b8a358152828b013583820152898b01358a8201526120de868c01611cd5565b86820152868b013587820152878b013588820152888b013589820152815201970196612085565b467f000000000000000000000000000000000000000000000000000000000000000103612150577fcaf223f64b5e007d059f6a459f3957a575131b4b0afce2012276a2a5de7dcc9f90565b604051600a545f9161216182611b6e565b8082528160209485820194600190876001821691825f14612245575050600114612208575b5061219392500382611c42565b5190209061219f611f04565b818151910120604051918201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a0815260c081018181106001600160401b03821117610f975760405251902090565b869150600a5f52815f20905f915b85831061222d57505061219393508201015f612186565b80548388018501528694508893909201918101612216565b60ff1916885261219395151560051b85010192505f91506121869050565b5f5c6001600160a01b03166122835761227e90335f5d612481565b5f805d565b61228c90612481565b565b9035601e19823603018112156105745701602081359101916001600160401b038211610574578160051b3603831361057457565b9190808252602080920192915f5b8281106122de575050505090565b909192938280600192838060a01b036122f689611cd5565b168152019501939291016122d0565b81835290916001600160fb1b0383116105745760209260051b809284830137010190565b908060209392818452848401375f828201840152601f01601f1916010190565b9035601e19823603018112156105745701602081359101916001600160401b03821161057457813603831361057457565b908281815260208091019360208360051b82010194845f925b8584106123a4575050505050505090565b9091929394959685806123cc600193601f198682030188526123c68c88612349565b90612329565b990194019401929594939190612393565b903590605e1981360301821215610574570190565b903590601e198136030182121561057457018035906001600160401b03821161057457602001918160051b3603831361057457565b9061243182611f9c565b61243e6040519182611c42565b828152809261244f601f1991611f9c565b0190602036910137565b805182101561246d5760209160051b010190565b634e487b7160e01b5f52603260045260245ffd5b6040918251602093848083015283359484360395605e198701811215610574576060848401526001600160a01b039690612507908701886124c182611cd5565b1660a08701526124f56124eb6124d98684018461228e565b606060c08b01526101008a01916122c2565b918681019061228e565b878303609f190160e089015290612305565b90868301359060be190181121561057457878786920192603f19938484830301606085015260c0820161253a828061228e565b60c085529182905260e08401915f5b89828210612782579050612573939250612566915084018461228e565b908583038a870152612305565b866125808984018461228e565b8584038b87015280845292909101915f5b8982821061275757505050509261262195926125e9836125cc612613976125be60606125f798018461228e565b908683036060880152612305565b936125d960808301611cd5565b16608084015260a081019061228e565b9160a081850391015261237a565b90612604878b018b61228e565b9185840301608086015261237a565b03601f198101855284611c42565b61264161263a61263187806123dd565b838101906123f2565b9050612427565b955f5b875181101561266257805f61265b6001938b612459565b5201612644565b5090919395929461267381806123dd565b35918216809203610574576126ad61269b6126a49461269284806123dd565b908101906123f2565b949092806123dd565b868101906123f2565b969093833b156105745761272c6126f3955f979361271789956127078c519d8e9b8c9a8b9963ab9c4b5d60e01b8b523060048c015260e060248c015260e48b01916122c2565b9060031996878a84030160448b0152612305565b9084878303016064880152611eb1565b913060848601528483030160a4850152611c84565b8260c483015203925af190811561274e57506127455750565b61228c90611bc1565b513d5f823e3d90fd5b919496839698509382919461276d600195611cd5565b1681520194019101908c94928a969492612591565b9194968396985093829194612798600195611cd5565b1681520194019101908c94928a969492612549565b6004356001600160a01b03811681036105745790565b356001600160a01b03811681036105745790565b9190820391821161144257565b604051906127f182611bf0565b6005546001600160a01b031682526006546020830152565b6040519061281682611bf0565b6003546001600160a01b031682526004546020830152565b999897969594939291905f5c6001600160a01b03166128535761227e9a335f5d61286c565b61228c9a61286c565b919081101561246d5760051b0190565b604051633af32abf60e01b81526001600160a01b03808b166004830152959b979a98999698919792959294919391927f000000000000000000000000307521f9650f6568bfe56521e3ed150f5a9878031690602081602481855afa908115610f12575f91612cbb575b5015612c6f5788850361111657604051633af32abf60e01b81526001600160a01b038816600482015290602090829060249082905afa908115610f12575f91612c81575b5015612c6f575f5c916001600160a01b03831615612c67575b5f5b868110612ab457505050505f5b868110612a3f5750906129539161419d565b5f5b81811061299c57505050505f5b818110612970575050505050565b80612996612984610df1600194868a61285c565b61298f83878961285c565b3590614335565b01612962565b6129b26129ad610df183858861285c565b6142ad565b5f6020816129c4610df185878a61285c565b60405163095ea7b360e01b81526001600160a01b03881660048201526024810183905260449485925af13d15601f3d1160015f511416171615612a0a5750600101612955565b6064906d1054141493d59157d1905253115160921b6040519162461bcd60e51b835260206004840152600e6024840152820152fd5b6001600160a01b03612a55610df1838a8e61285c565b1690813b156105745760405163a22cb46560e01b81526001600160a01b038716600482015260016024820152915f908390604490829084905af1918215610f1257600192612aa5575b5001612941565b612aae90611bc1565b5f612a9e565b6001600160a01b03612aca610df1838a8d61285c565b604051633af32abf60e01b8152911660048201526020816024817f0000000000000000000000004150ded32a6d3bfecae76e7558af4801903449276001600160a01b03165afa908115610f12575f91612c2d575b501561194e5760249060206001600160a01b03612b3f610df1848c8f61285c565b16604051938480926370a0823160e01b82523060048301525afa918215610f125783858a948d935f91612bec575b50600195612baf94848488858196612b8c83612b9c9b610df19b61285c565b3511612bb5575b5050505061285c565b8a612ba884878961285c565b35916132b7565b01612934565b612bd482612be395612bce610df1612bda968b8b61285c565b9561285c565b356127d7565b908d30916138b0565b888a845f612b93565b9550505050506020823d602011612c25575b81612c0b60209383611c42565b810103126105745790518791908a90849086906001612b6d565b3d9150612bfe565b90506020813d602011612c5f575b81612c4860209383611c42565b8101031261057457612c5990611f8f565b5f612b1e565b3d9150612c3b565b339250612932565b604051633c855b1f60e01b8152600490fd5b90506020813d602011612cb3575b81612c9c60209383611c42565b8101031261057457612cad90611f8f565b5f612919565b3d9150612c8f565b90506020813d602011612ced575b81612cd660209383611c42565b8101031261057457612ce790611f8f565b5f6128d5565b3d9150612cc9565b9080601f83011215610574576020908235612d0f81611f9c565b93612d1d6040519586611c42565b81855260208086019260051b82010192831161057457602001905b828210612d46575050505090565b81358152908301908301612d38565b81601f8201121561057457803591602091612d6f84611f9c565b93612d7d6040519586611c42565b808552838086019160051b8301019280841161057457848301915b848310612da85750505050505090565b82356001600160401b038111610574578691612dc984848094890101611d3e565b815201920191612d98565b90808251908181526020809101926020808460051b8301019501935f915b848310612e025750505050505090565b9091929394958480612e20600193601f198682030187528a51611c84565b9801930193019194939290612df2565b81601f82011215610574578051612e4681611d23565b92612e546040519485611c42565b8184526020828401011161057457612e729160208085019101611c63565b90565b9060209081838203126105745782516001600160401b0393848211610574570181601f82011215610574578051612eab81611f9c565b94612eb96040519687611c42565b818652848087019260051b8401019380851161057457858401925b858410612ee5575050505050505090565b8351838111610574578791612eff848480948a0101612e30565b815201930192612ed4565b6004356001600160801b03811681036105745790565b6024356001600160801b03811681036105745790565b6040810191906001600160801b0390600435828116908190036105745781526024359182168092036105745760200152565b9594939291905f5c6001600160a01b0316612f895761227e96335f5d612f8e565b61228c965b956001600160a01b0395947f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc28716949293929091853b1561057457604097885195630d0e30db60e41b87525f9a6004975f818a818d34905af180156131ec576131cf575b50916130018c939284936144e4565b600f54168a51848b8237828186810182815203925af161301f613bcc565b90156131a7575087519061303282611bf0565b6001825260209788830191893684378088116131a357908761305792820191016140da565b515190825115613190576130a36130ab9695936129ad95937fa27cb51fa01cdd60d7aa111f0ca97a7499b926814da4ab7a73887e03df9c698093528b519182918c83528c830190611eb1565b0390a1614335565b83516370a0823160e01b815230828201528381602481865afa938415613186578694613156575b5050813b156131525782602486928387519586948593632e1a7d4d60e01b85528401525af1801561314557908491613131575b5080808093818115613128575b3390f11561311e575050565b51903d90823e3d90fd5b506108fc613112565b61313a90611bc1565b61057857825f613105565b50505051903d90823e3d90fd5b8480fd5b9080929450813d831161317f575b61316e8183611c42565b810103126105745751915f806130d2565b503d613164565b85513d88823e3d90fd5b603287634e487b7160e01b5f525260245ffd5b8b80fd5b8561082f8b928b8051948594631b3dcf4560e21b865285015260248401526044830190611c84565b6130019c506131e19093919293611bc1565b5f9b92919092612ff2565b8c513d5f823e3d90fd5b60018060a01b038082511691816020820151169180604083015116916060810151916080820151169060a081015160c08201519260e083015194610100809401511515966040519860208a019a7f423fab2f01310f469c043522a820d13315f9fbfe2f7ea3fc78e36d28b60cc5118c5260408b015260608a0152608089015260a088015260c087015260e08601528401526101208301526101409081830152815261016081018181106001600160401b03821117610f975760405251902090565b60405163095ea7b360e01b81526001600160a01b03909216600483015260248201929092526020915f9160449183905af13d15601f3d1160015f5114161716156132fd57565b60405162461bcd60e51b815260206004820152600e60248201526d1054141493d59157d1905253115160921b6044820152606490fd5b906020828203126105745781356001600160401b0392838211610574570191604083830312610574576040519261336984611bf0565b803582811161057457810192606084820312610574576040519061338c82611ba6565b61339585611cd5565b8252602085013593841161057457846133b76040926020966133ca9801611d3e565b8584015201356040820152845201611d16565b602082015290565b91906133e091810190613333565b8051602091820151815160408051633af32abf60e01b81526001600160a01b039283166004808301919091529591945f939183816024817f000000000000000000000000307521f9650f6568bfe56521e3ed150f5a98780387165afa90811561382e575f916137f9575b50156137e95780517f000000000000000000000000b47e3cd837ddf8e4c57f05d70ab865de6e193bbb831695908316868103613608575050818689015116938260608a01511693808a0151928881015196858c511690803b15610574575f8c87936134c88e51998a9384936323b872dd60e01b855230918501613bfb565b038183855af180156135fe576135e3575b849550849392916134e991614c6d565b8282878351169201519283519301915af194613503613bcc565b50803b156106c75781809189895180948193633ccfd60b60e01b83525af180156135c5579082916135cf575b5050817f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc216803b156106c75781859189895180948193630d0e30db60e41b83525af180156135c5576135b1575b50509061358d929187511690614467565b156135a457505061228c90600d5460801c90614a50565b5163275d4ca360e11b8152fd5b6135bb8291611bc1565b610244578061357c565b87513d84823e3d90fd5b6135d890611bc1565b61024457805f61352f565b92919093506135f46134e995611bc1565b5f939091926134d9565b8b513d5f823e3d90fd5b90919293945030145f146137d357508201519081518201838382031261057457838301516001600160401b03938482116105745701926101409182858203126105745787519261365784611bd4565b613662878701614a3c565b845261366f898701614a3c565b8785015261367f60608701614a3c565b918985019283526080870151966060860197885261369f60a08201614a3c565b9860808701998a5260c082015160a088015260e082015160c0880152610100938483015160e0890152610120946136d7868501611f8f565b90890152820151958611610574578795816136f59401920101612e30565b908401525116918060608a0151169351935f5c95828716156137cb575b518216036137bb57868901511682036137ab57613730903090614684565b61373981614b11565b613749575b50505050600161358d565b8161375391614c6d565b823b156105745761377b925f928386518096819582946322dca8bb60e21b84528b8401613c1d565b03925af180156137a157613792575b80808061373e565b61379b90611bc1565b5f61378a565b82513d5f823e3d90fd5b8551630539ad7d60e11b81528790fd5b8651632d0ad63f60e11b81528890fd5b339650613712565b6137e4945015159250905085614901565b61358d565b8551633c855b1f60e01b81528790fd5b90508381813d8311613827575b6138108183611c42565b810103126105745761382190611f8f565b5f61344a565b503d613806565b87513d5f823e3d90fd5b6001600160a01b03161561384857565b6040516349431df560e11b8152600490fd5b60018060a01b03808251165f52600960205260405f209060c083019182515f5260205260ff60405f205416928315613893575b50505090565b9091925051165f52600860205260405f20549051105f808061388d565b915f8093602095606494604051946323b872dd60e01b865260018060a01b03809216600487015216602485015260448401525af13d15601f3d1160015f5114161716156138f957565b60405162461bcd60e51b81526020600482015260146024820152731514905394d1915497d19493d357d1905253115160621b6044820152606490fd5b903590601e198136030182121561057457018035906001600160401b038211610574576020019160c082023603831361057457565b919081101561246d5760c0020190565b90604080519061398982611c0b565b5f82526020935f858401525f8284015260605f8185015260805f8186015260a0915f8387015260c0945f60c08801528160e08801525f6101008098015260e08501956139d58787613935565b9190506139e182611f9c565b976139ee8451998a611c42565b828952601f196139fd84611f9c565b018c875f878b8a8f5b878510613b8d5750505050505050505f5b838110613aa257505050509160c09391613a5c98998694613a3a5f9a99986127c3565b90613a468188016127c3565b91613a528589016127c3565b9282519d8e611c0b565b6001600160a01b039182168e5288850135948e0194909452928316908c0152169089015280830135908801528082013590870152013560c085015260e084015282015290565b80613aad838b613935565b613ab7929161396a565b358d8389898d8d878c81613acb8886613935565b613ad5929161396a565b0135928782613ae48984613935565b613aee929161396a565b01613af8906127c3565b9482613b048984613935565b613b0e929161396a565b01359582613b1c8984613935565b613b26929161396a565b013596613b3291613935565b613b3c929161396a565b0135948b5196613b4b88611c27565b87525f908701528a8601526001600160a01b03168a8501528a8401528a83015284820152613b79828c612459565b52613b84818b612459565b50600101613a17565b5f879693818895818996818a519a613ba48c611c27565b818c52818b8d01528b01528901528701528501525f8a850152010152018890878b8a8f613a06565b3d15613bf6573d90613bdd82611d23565b91613beb6040519384611c42565b82523d5f602084013e565b606090565b6001600160a01b03918216815291166020820152604081019190915260600190565b6001600160a01b039091168152602081019190915260400190565b9291613c479192810190613333565b8051805160408051633af32abf60e01b81526001600160a01b039283166004808301919091525f9692959094909260209283816024817f000000000000000000000000307521f9650f6568bfe56521e3ed150f5a9878038a165afa908115613fd6575f91614000575b5015613ff0578460608a01511690857f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc216809203613fe057613cf69060808b01516127d7565b90803b15610574575f809160248a5180948193632e1a7d4d60e01b8352878d8401525af18015613fd657613fc3575b508482511688808985019283519087870151918883519301915af1613d48613bcc565b5015613fb3578592918991518b818311613f7b575b50505050511691837f000000000000000000000000b47e3cd837ddf8e4c57f05d70ab865de6e193bbb168093145f14613efa57508601908151813b15613ee257613ddf8792839288519485809481936322dca8bb60e21b83527f0000000000000000000000006a4b2696cdef4252968d5994626cc99a365e01ee8c8401613c1d565b03925af1801561318657908691613ee6575b5050817f000000000000000000000000b7f7f6c52f2e2fdb1963eab30438024864c313f616918151833b15613ee25785519063140e25ad60e31b825285820152868160248183885af18015613ed857908791613ec4575b5050865116905192823b156105705791849391868094613e7d9751978895869485936323b872dd60e01b855230908501613bfb565b03925af1908115613ebb5750613ea7575b505061228c905b600d546001600160801b031690614a50565b613eb18291611bc1565b6102445780613e8e565b513d84823e3d90fd5b613ecd90611bc1565b61057057855f613e48565b86513d89823e3d90fd5b8680fd5b613eef90611bc1565b61315257845f613df1565b8192500151613f12575b505050505061228c90613e95565b818487015116918651169086015192823b156105705791849391868094613f4e9751978895869485936323b872dd60e01b855230908501613bfb565b03925af1908115613ebb5750613f67575b808080613f04565b613f718291611bc1565b6102445780613f5f565b91613f908495969285949385945116926127d7565b90828215613faa575bf115613186578390875f808b613d5d565b506108fc613f99565b875163275d4ca360e11b81528790fd5b613fce919850611bc1565b5f965f613d25565b88513d5f823e3d90fd5b87516316ad1d1360e31b81528790fd5b8651633c855b1f60e01b81528690fd5b90508381813d831161402e575b6140178183611c42565b810103126105745761402890611f8f565b5f613cb0565b503d61400d565b91602081602085016020865252604084019360408360051b82010194845f925b858410614066575050505050505090565b909192939495968580614088600193603f198682030188526123c68c88612349565b990194019401929594939190614055565b919081101561246d5760051b81013590601e19813603018212156105745701908135916001600160401b038311610574576020018236038113610574579190565b906020828203126105745781356001600160401b039283821161057457016060818303126105745760409182519361411185611ba6565b82358181116105745783016060818403126105745784519061413282611ba6565b803582526020810135838111610574578161415386899361415e9501611d3e565b602085015201611d16565b8582015285526020830135818111610574578261417c918501611fb3565b602086015283830135908111610574576141969201611d3e565b9082015290565b9060018060a01b03600f541690604090604051631592ca1b60e31b81525f816004958183816141d0888c60048401614035565b03925af18015610f1257614293575b506141e981612427565b935f5b8281106142365750505050506142317fa27cb51fa01cdd60d7aa111f0ca97a7499b926814da4ab7a73887e03df9c698091604051918291602083526020830190611eb1565b0390a1565b84614242828585614099565b9050111561428357614255818484614099565b808793929311610574578287614270926001950191016140da565b515161427c8289612459565b52016141ec565b505050516324017b7b60e21b8152fd5b6142a6903d805f833e610ee58183611c42565b505f6141df565b6040516370a0823160e01b81523060048201526001600160a01b036020826024818685165afa918215610f12575f92614301575b50816142ec57505050565b61228c925f5c91821661446757339150614467565b9091506020813d60201161432d575b8161431d60209383611c42565b810103126105745751905f6142e1565b3d9150614310565b9061433f82614b11565b1561440e57604051630b02f02d60e31b8152600481018290526001600160a01b037f000000000000000000000000b47e3cd837ddf8e4c57f05d70ab865de6e193bbb81169391929190602084602481885afa938415610f12575f9461442b575b505f5c9381851615614423575b81169080831682141580614417575b156143fe57505050823b15610574576143ed925f92836040518096819582946322dca8bb60e21b845260048401613c1d565b03925af18015610f12576127455750565b9194509192160361440e575b5050565b61228c91614b75565b508085168214156143bb565b3394506143ac565b9093506020813d60201161445f575b8161444760209383611c42565b810103126105745761445890614a3c565b925f61439f565b3d915061443a565b60405163a9059cbb60e01b81526001600160a01b03909216600483015260248201929092526020915f9160449183905af13d15601f3d1160015f5114161716156144ad57565b60405162461bcd60e51b815260206004820152600f60248201526e1514905394d1915497d19052531151608a1b6044820152606490fd5b5f91801561467f576001600160a01b037f0000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad811691907f000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba3811690813b15610574575f916084839260405194859384926387517c4560e01b8452807f000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc216600485015289602485015260448401528160648401525af18015610f125761466c575b508201606083820312614668576001600160401b0390833582811161057057816145cd918601611d3e565b91602085013590811161057057906145e6918501612d55565b92823b15613152579161462085614632959381956040805198899788968795630d64d59360e21b8752606060048801526064870190611c84565b85810360031901602487015290612dd4565b910135604483015203925af1801561465d5761464c575050565b6146568291611bc1565b6102445750565b6040513d84823e3d90fd5b8380fd5b614677919450611bc1565b5f925f6145a2565b505050565b9061468d612105565b906146ce6146c5604261469f866131f6565b9460409586519161190160f01b8352600283015260228201522061012086015190614d5c565b90929192614d96565b83516001600160a01b0391908216908216036148f05760e0840191825142116148df576146fa8561385a565b6148cb578160208601511680151590816148c0575b506148af57608085019382855116610100870190815115155f146148155761474190858951168560a08b015192614c47565b838288015116848851166060890151823b156105745761477b92865f8094885196879586948593632142170760e11b855260048501613bfb565b03925af1801561480b57917f0dfa32898287c90958a4053360eb412a321b5bec5f982f2afbf826bc21ad81fc95939160c095936147fc575b505b838951169684838b015116998560608201519a5116938760a08301519201519251935115159481519b8c5260208c01528a01526060890152608088015260a08701521693a4565b61480590611bc1565b5f6147b3565b83513d5f823e3d90fd5b6148299084868a511660a08b015192614c47565b838288015116848851166060890151823b1561057457614862925f92838751809681958294632142170760e11b84528c60048501613bfb565b03925af1801561480b57917f0dfa32898287c90958a4053360eb412a321b5bec5f982f2afbf826bc21ad81fc95939160c095936148a0575b506147b5565b6148a990611bc1565b5f61489a565b83516307df9d7f60e21b8152600490fd5b90503314155f61470f565b83516001622a81af60e21b03198152600490fd5b83516362b439dd60e11b8152600490fd5b8251638baa579f60e01b8152600490fd5b9192909215614a0e57604080830151935f9460018060a01b03809116918160608701511692602087015194808301519584895116833b15610574575f8261495e92855193849283926323b872dd60e01b8452309060048501613bfb565b038183885af1801561480b576149fb575b5089908585511693803b156105785761499e9483855180978195829463095ea7b360e01b845260048401613c1d565b03925af19081156149f257506149de575b5095808760208461228c98999a51169101519082602083519301915af16149d4613bcc565b5095511690614467565b6149e88891611bc1565b613ee2575f6149af565b513d8a823e3d90fd5b614a06919a50611bc1565b5f985f61496f565b8251602093840151805192945f945084939291019083906001600160a01b03165af1614a38613bcc565b5090565b51906001600160a01b038216820361057457565b811561440a57614a5e612809565b9160018060a01b036060908060608501511692818551169260e05f960190815151925f9260208a01935b858110614ab85750505050505083614aa2575b5050505050565b614aae945116916138b0565b5f80808080614a9b565b80825190614ac591612459565b51998360408c015190614ad791614d3a565b88868851614ae59084614d3a565b9384614af091611f5c565b9d01511691614afe916127d7565b614b09918a8c6138b0565b600101614a88565b6001600160a01b039081167f000000000000000000000000b7f7f6c52f2e2fdb1963eab30438024864c313f682168114918215614b4d57505090565b7f000000000000000000000000b7f7f6c52f2e2fdb1963eab30438024864c313f61614919050565b6040516331a9108f60e11b8152600481018390526001600160a01b039182169290602081602481875afa908115610f12575f91614c0d575b50825f5c81811615614c06575b8116911603614bc857505050565b5f5c91821615614bfe575b823b15610574576143ed925f9283604051809681958294632142170760e11b84523060048501613bfb565b339150614bd3565b5033614bba565b90506020813d602011614c3f575b81614c2860209383611c42565b8101031261057457614c3990614a3c565b5f614bad565b3d9150614c1b565b9291906001600160a01b0381163003614c64575061228c92614467565b61228c936138b0565b6001600160a01b037f000000000000000000000000b7f7f6c52f2e2fdb1963eab30438024864c313f681169291811690838203614cd9575050813b15610574575f91602483926040519485938492639f8f573f60e01b845260048401525af18015610f12576127455750565b7f000000000000000000000000b7f7f6c52f2e2fdb1963eab30438024864c313f61692508214614d07575050565b813b15610574575f91602483926040519485938492630852cd8d60e31b845260048401525af18015610f12576127455750565b9061271091815f19048111820215830215610574570290808204910615150190565b8151919060418303614d8c57614d859250602082015190606060408401519301515f1a90614e19565b9192909190565b50505f9160029190565b6004811015614e055780614da8575050565b60018103614dc25760405163f645eedf60e01b8152600490fd5b60028103614de35760405163fce698f760e01b815260048101839052602490fd5b600314614ded5750565b602490604051906335e2f38360e21b82526004820152fd5b634e487b7160e01b5f52602160045260245ffd5b91906fa2a8918ca85bafe22016d0b997e4df60600160ff1b038411614e86579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610f12575f516001600160a01b03811615614e7c57905f905f90565b505f906001905f90565b5050505f916003919056fea26469706673582212200f168c073d04bff931e106003358fc63c96bea3e0abb6a271b8b6e3f98020eb064736f6c63430008180033
Loading...
Loading
Loading...
Loading
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.