/**
* Created by Pragma Labs
* SPDX-License-Identifier: BUSL-1.1
*/
pragma solidity 0.8.22;
// Struct with risk and valuation related information for a certain asset.
struct AssetValueAndRiskFactors {
// The value of the asset.
uint256 assetValue;
// The collateral factor of the asset, for a given creditor.
uint256 collateralFactor;
// The liquidation factor of the asset, for a given creditor.
uint256 liquidationFactor;
}
/**
* @title Asset Valuation Library
* @author Pragma Labs
* @notice The Asset Valuation Library is responsible for calculating the risk weighted values of combinations of assets.
*/
library AssetValuationLib {
/*///////////////////////////////////////////////////////////////
CONSTANTS
///////////////////////////////////////////////////////////////*/
uint256 internal constant ONE_4 = 10_000;
/*///////////////////////////////////////////////////////////////
RISK FACTORS
///////////////////////////////////////////////////////////////*/
/**
* @notice Calculates the collateral value given a combination of asset values and corresponding collateral factors.
* @param valuesAndRiskFactors Array of asset values and corresponding collateral factors.
* @return collateralValue The collateral value of the given assets.
*/
function _calculateCollateralValue(AssetValueAndRiskFactors[] memory valuesAndRiskFactors)
internal
pure
returns (uint256 collateralValue)
{
for (uint256 i; i < valuesAndRiskFactors.length; ++i) {
collateralValue += valuesAndRiskFactors[i].assetValue * valuesAndRiskFactors[i].collateralFactor;
}
collateralValue = collateralValue / ONE_4;
}
/**
* @notice Calculates the liquidation value given a combination of asset values and corresponding liquidation factors.
* @param valuesAndRiskFactors List of asset values and corresponding liquidation factors.
* @return liquidationValue The liquidation value of the given assets.
*/
function _calculateLiquidationValue(AssetValueAndRiskFactors[] memory valuesAndRiskFactors)
internal
pure
returns (uint256 liquidationValue)
{
for (uint256 i; i < valuesAndRiskFactors.length; ++i) {
liquidationValue += valuesAndRiskFactors[i].assetValue * valuesAndRiskFactors[i].liquidationFactor;
}
liquidationValue = liquidationValue / ONE_4;
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
pragma solidity 0.8.22;
// solhint-disable
/**
* @dev Reverts if `condition` is false, with a revert reason containing `errorCode`. Only codes up to 999 are
* supported.
*/
function _require(bool condition, uint256 errorCode) pure {
if (!condition) {
_revert(errorCode);
}
}
/**
* @dev Reverts with a revert reason containing `errorCode`. Only codes up to 999 are supported.
*/
function _revert(uint256 errorCode) pure {
// We're going to dynamically create a revert string based on the error code, with the following format:
// 'BAL#{errorCode}'
// where the code is left-padded with zeroes to three digits (so they range from 000 to 999).
//
// We don't have revert strings embedded in the contract to save bytecode size: it takes much less space to store a
// number (8 to 16 bits) than the individual string characters.
//
// The dynamic string creation algorithm that follows could be implemented in Solidity, but assembly allows for a
// much denser implementation, again saving bytecode size. Given this function unconditionally reverts, this is a
// safe place to rely on it without worrying about how its usage might affect e.g. memory contents.
assembly {
// First, we need to compute the ASCII representation of the error code. We assume that it is in the 0-999
// range, so we only need to convert three digits. To convert the digits to ASCII, we add 0x30, the value for
// the '0' character.
let units := add(mod(errorCode, 10), 0x30)
errorCode := div(errorCode, 10)
let tenths := add(mod(errorCode, 10), 0x30)
errorCode := div(errorCode, 10)
let hundreds := add(mod(errorCode, 10), 0x30)
// With the individual characters, we can now construct the full string. The "BAL#" part is a known constant
// (0x42414c23): we simply shift this by 24 (to provide space for the 3 bytes of the error code), and add the
// characters to it, each shifted by a multiple of 8.
// The revert reason is then shifted left by 200 bits (256 minus the length of the string, 7 characters * 8 bits
// per character = 56) to locate it in the most significant part of the 256 slot (the beginning of a byte
// array).
let revertReason := shl(200, add(0x42414c23000000, add(add(units, shl(8, tenths)), shl(16, hundreds))))
// We can now encode the reason in memory, which can be safely overwritten as we're about to revert. The encoded
// message will have the following layout:
// [ revert reason identifier ] [ string location offset ] [ string length ] [ string contents ]
// The Solidity revert reason identifier is 0x08c739a0, the function selector of the Error(string) function. We
// also write zeroes to the next 28 bytes of memory, but those are about to be overwritten.
mstore(0x0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
// Next is the offset to the location of the string, which will be placed immediately after (20 bytes away).
mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020)
// The string length is fixed: 7 characters.
mstore(0x24, 7)
// Finally, the string itself is stored.
mstore(0x44, revertReason)
// Even if the string is only 7 bytes long, we need to return a full 32 byte slot containing it. The length of
// the encoded message is therefore 4 + 32 + 32 + 32 = 100.
revert(0, 100)
}
}
library Errors {
// Math
uint256 internal constant X_OUT_OF_BOUNDS = 6;
uint256 internal constant Y_OUT_OF_BOUNDS = 7;
uint256 internal constant PRODUCT_OUT_OF_BOUNDS = 8;
uint256 internal constant INVALID_EXPONENT = 9;
}
/**
* Created by Pragma Labs
* SPDX-License-Identifier: BUSL-1.1
*/
pragma solidity 0.8.22;
import { GuardianErrors } from "../libraries/Errors.sol";
import { Owned } from "../../lib/solmate/src/auth/Owned.sol";
/**
* @title Guardian
* @author Pragma Labs
* @notice Abstract contract with the minimal implementation of a Guardian contract.
* It implements the following logic:
* - An authorized guardian can trigger an emergency stop.
* - The protocol owner can unpause functionalities one-by-one.
* - Anyone can unpause all functionalities after a fixed cool-down period.
*/
abstract contract BaseGuardian is Owned {
/* //////////////////////////////////////////////////////////////
STORAGE
////////////////////////////////////////////////////////////// */
// Last timestamp an emergency stop was triggered.
uint96 public pauseTimestamp;
// Address of the Guardian.
address public guardian;
/* //////////////////////////////////////////////////////////////
EVENTS
////////////////////////////////////////////////////////////// */
event GuardianChanged(address indexed user, address indexed newGuardian);
/* //////////////////////////////////////////////////////////////
MODIFIERS
////////////////////////////////////////////////////////////// */
/**
* @dev Only guardians can call functions with this modifier.
*/
modifier onlyGuardian() {
if (msg.sender != guardian) revert GuardianErrors.OnlyGuardian();
_;
}
/**
* @dev The public unpause() function, or a second pause() function, can only called a fixed coolDownPeriod after an initial pause().
* This gives the protocol owner time to investigate and solve potential issues,
* but ensures that no rogue owner or guardian can lock user funds for an indefinite amount of time.
*/
modifier afterCoolDownOf(uint256 coolDownPeriod) {
if (block.timestamp <= pauseTimestamp + coolDownPeriod) revert GuardianErrors.CoolDownPeriodNotPassed();
_;
}
/* //////////////////////////////////////////////////////////////
CONSTRUCTOR
////////////////////////////////////////////////////////////// */
constructor() Owned(msg.sender) { }
/* //////////////////////////////////////////////////////////////
GUARDIAN LOGIC
////////////////////////////////////////////////////////////// */
/**
* @notice This function is used to set the guardian address
* @param guardian_ The address of the new guardian.
*/
function changeGuardian(address guardian_) external onlyOwner {
emit GuardianChanged(msg.sender, guardian = guardian_);
}
/* //////////////////////////////////////////////////////////////
PAUSING LOGIC
////////////////////////////////////////////////////////////// */
/**
* @notice This function is used to pause all the flags of the contract.
* @dev The Guardian can only pause the protocol again after 32 days have passed since the last pause.
* This is to prevent that a malicious owner or guardian can take user funds hostage for an indefinite time.
* After the guardian has paused the protocol, the owner has 30 days to find potential problems,
* find a solution and unpause the protocol. If the protocol is not unpaused after 30 days,
* an emergency procedure can be started by any user to unpause the protocol.
* All users have now at least a two-day window to withdraw assets and close positions before
* the protocol can again be paused 32 days after the contract was previously paused.
*/
function pause() external virtual;
/**
* @notice This function is used to unpause flags that could be abused to lock user assets.
* @dev If the protocol is not unpaused after 30 days, any user can unpause the protocol.
* This ensures that no rogue owner or guardian can lock user funds for an indefinite amount of time.
* All users have now at least a two-day window to withdraw assets and close positions before
* the protocol can again be paused 32 days after the contract was previously paused.
*/
function unpause() external virtual;
}
/**
* Created by Pragma Labs
* SPDX-License-Identifier: BUSL-1.1
*/
pragma solidity 0.8.22;
import { ICreditor } from "../interfaces/ICreditor.sol";
/**
* @title Creditor.
* @author Pragma Labs
* @notice See the documentation in ICreditor
*/
abstract contract Creditor is ICreditor {
/* //////////////////////////////////////////////////////////////
STORAGE
////////////////////////////////////////////////////////////// */
// The address of the riskManager.
address public riskManager;
// The Account for which a flashAction is called.
address internal callbackAccount;
// Map accountVersion => status.
mapping(uint256 => bool) public isValidVersion;
/* //////////////////////////////////////////////////////////////
EVENTS
////////////////////////////////////////////////////////////// */
event RiskManagerUpdated(address riskManager);
event ValidAccountVersionsUpdated(uint256 indexed accountVersion, bool valid);
/* //////////////////////////////////////////////////////////////
ERRORS
////////////////////////////////////////////////////////////// */
// Thrown when caller is not authorized.
error Unauthorized();
/* //////////////////////////////////////////////////////////////
CONSTRUCTOR
////////////////////////////////////////////////////////////// */
/**
* @param riskManager_ The address of the Risk Manager.
*/
constructor(address riskManager_) {
_setRiskManager(riskManager_);
}
/* //////////////////////////////////////////////////////////////
ACCOUNT LOGIC
////////////////////////////////////////////////////////////// */
/**
* @notice Sets a new Risk Manager. A Risk Manager can:
* - Set risk parameters for collateral assets, including: max exposures, collateral factors and liquidation factors.
* - Set minimum usd values for assets in order to be taken into account, to prevent dust attacks.
* @param riskManager_ The address of the new Risk Manager.
*/
function _setRiskManager(address riskManager_) internal {
riskManager = riskManager_;
emit RiskManagerUpdated(riskManager_);
}
/**
* @notice Updates the validity of an Account version.
* @param accountVersion The Account version.
* @param isValid Will be "true" if respective Account version is valid, "false" if not.
*/
function _setAccountVersion(uint256 accountVersion, bool isValid) internal {
isValidVersion[accountVersion] = isValid;
emit ValidAccountVersionsUpdated(accountVersion, isValid);
}
/**
* @inheritdoc ICreditor
* @dev This function response is used for the Arcadia Accounts margin account creation.
* This function does not deploy a new Arcadia Account.
* It just provides the parameters to be used in Arcadia Account to connect to the Creditor.
*/
function openMarginAccount(uint256 accountVersion)
external
virtual
returns (bool success, address numeraire, address liquidator, uint256 minimumMargin);
/**
* @inheritdoc ICreditor
* @dev This function checks if the given Account address has an open position. If not, it can be closed.
*/
function closeMarginAccount(address account) external virtual;
/**
* @inheritdoc ICreditor
* @dev The open position is the sum of all liabilities.
*/
function getOpenPosition(address account) external view virtual returns (uint256 openPosition);
/**
* @inheritdoc ICreditor
* @dev During the callback, the Account cannot be reentered and the actionTimestamp is updated.
*/
function flashActionCallback(bytes calldata callbackData) external virtual {
// Only the Account for which the flashAction is initiated is allowed to callback.
if (callbackAccount != msg.sender) revert Unauthorized();
// Reset callbackAccount, the Account should only callback once during a flashAction.
callbackAccount = address(0);
_flashActionCallback(msg.sender, callbackData);
}
/**
* @notice Callback of the Account during a flashAction.
* @param account The contract address of the Arcadia Account.
* @param callbackData The data for the actions that have to be executed by the Creditor during a flashAction.
* @dev During the callback, the Account cannot be reentered and the actionTimestamp is updated.
*/
function _flashActionCallback(address account, bytes calldata callbackData) internal virtual;
/**
* @inheritdoc ICreditor
* @dev Starts the liquidation process in the Creditor.
* This function should be callable by Arcadia Account.
*/
function startLiquidation(address initiator, uint256 minimumMargin)
external
virtual
returns (uint256 openPosition);
}
/**
* Created by Pragma Labs
* SPDX-License-Identifier: BUSL-1.1
*/
pragma solidity 0.8.22;
import { ERC20, ERC4626 } from "../lib/solmate/src/mixins/ERC4626.sol";
import { FixedPointMathLib } from "../lib/solmate/src/utils/FixedPointMathLib.sol";
import { DebtTokenErrors } from "./libraries/Errors.sol";
/**
* @title Debt Token.
* @author Pragma Labs
* @notice The Logic to do the debt accounting for a lending pool for a certain ERC20 token.
* @dev Protocol is according the ERC4626 standard, with a certain ERC20 as underlying.
* @dev Implementation slightly deviates from the ERC4626 specifications,
* maxDeposit() and maxMint() are not implemented.
*/
abstract contract DebtToken is ERC4626 {
using FixedPointMathLib for uint256;
/* //////////////////////////////////////////////////////////////
STORAGE
////////////////////////////////////////////////////////////// */
// Total amount of `underlying asset` that debtors have in debt, does not take into account pending interests.
uint256 internal realisedDebt;
/* //////////////////////////////////////////////////////////////
CONSTRUCTOR
////////////////////////////////////////////////////////////// */
/**
* @notice The constructor for the debt token.
* @param asset_ The underlying ERC20 token in which the debt is denominated.
*/
constructor(ERC20 asset_)
ERC4626(
asset_,
string(abi.encodePacked("ArcadiaV2 ", asset_.name(), " Debt")),
string(abi.encodePacked("darcV2", asset_.symbol()))
)
{ }
/*//////////////////////////////////////////////////////////////
ACCOUNTING LOGIC
//////////////////////////////////////////////////////////////*/
/**
* @notice Returns the total amount of outstanding debt in the underlying asset.
* @return totalDebt The total debt in underlying assets.
* @dev Implementation overwritten in LendingPool.sol which inherits DebtToken.sol.
* Implementation not vulnerable to ERC4626 inflation attacks,
* totalAssets() does not rely on balanceOf call.
*/
function totalAssets() public view virtual override returns (uint256);
/*//////////////////////////////////////////////////////////////
DEPOSIT/WITHDRAWAL LOGIC
//////////////////////////////////////////////////////////////*/
/**
* @notice Modification of the standard ERC4626 deposit implementation.
* @dev No public deposit allowed.
*/
function deposit(uint256, address) public pure override returns (uint256) {
revert DebtTokenErrors.FunctionNotImplemented();
}
/**
* @notice Modification of the standard ERC4626 deposit implementation.
* @param assets The amount of assets of the underlying ERC20 token being loaned out.
* @param receiver The Arcadia Account with collateral covering the debt.
* @return shares The corresponding amount of debt shares minted.
* @dev Only the Lending Pool (which inherits this contract) can issue debt.
*/
function _deposit(uint256 assets, address receiver) internal returns (uint256 shares) {
// No need to check for rounding error, previewDeposit rounds up.
shares = previewDeposit(assets);
_mint(receiver, shares);
realisedDebt += assets;
emit Deposit(msg.sender, receiver, assets, shares);
}
/**
* @notice Modification of the standard ERC4626 deposit implementation.
* @dev No public mint allowed.
*/
function mint(uint256, address) public pure override returns (uint256) {
revert DebtTokenErrors.FunctionNotImplemented();
}
/**
* @notice Modification of the standard ERC4626 withdraw implementation.
* @dev No public withdraw allowed.
*/
function withdraw(uint256, address, address) public pure override returns (uint256) {
revert DebtTokenErrors.FunctionNotImplemented();
}
/**
* @notice Modification of the standard ERC4626 withdraw implementation.
* @param assets The amount of assets of the underlying ERC20 token being paid back.
* @param receiver Will always be the Lending Pool.
* @param account The Arcadia Account with collateral covering the debt.
* @return shares The corresponding amount of debt shares redeemed.
* @dev Only the Lending Pool (which inherits this contract) can issue debt.
*/
function _withdraw(uint256 assets, address receiver, address account) internal returns (uint256 shares) {
// Check for rounding error since we round down in previewWithdraw.
if ((shares = previewWithdraw(assets)) == 0) revert DebtTokenErrors.ZeroShares();
_burn(account, shares);
realisedDebt -= assets;
emit Withdraw(msg.sender, receiver, account, assets, shares);
}
/**
* @notice Modification of the standard ERC4626 redeem implementation.
* @dev No public redeem allowed.
*/
function redeem(uint256, address, address) public pure override returns (uint256) {
revert DebtTokenErrors.FunctionNotImplemented();
}
/*//////////////////////////////////////////////////////////////
ACCOUNTING LOGIC
//////////////////////////////////////////////////////////////*/
/**
* @notice Modification of the standard ERC4626 convertToShares implementation.
* @dev Since debt is a liability instead of an asset, roundUp and roundDown are inverted compared to the standard implementation.
*/
function convertToShares(uint256 assets) public view override returns (uint256) {
// Cache totalSupply.
uint256 supply = totalSupply;
return supply == 0 ? assets : assets.mulDivUp(supply, totalAssets());
}
/**
* @notice Modification of the standard ERC4626 convertToShares implementation.
* @dev Since debt is a liability instead of an asset, roundUp and roundDown are inverted compared to the standard implementation.
*/
function convertToAssets(uint256 shares) public view override returns (uint256) {
// Cache totalSupply.
uint256 supply = totalSupply;
return supply == 0 ? shares : shares.mulDivUp(totalAssets(), supply);
}
/**
* @notice Modification of the standard ERC4626 previewMint implementation.
* @dev Since debt is a liability instead of an asset, roundUp and roundDown are inverted compared to the standard implementation.
*/
function previewMint(uint256 shares) public view override returns (uint256) {
// Cache totalSupply.
uint256 supply = totalSupply;
return supply == 0 ? shares : shares.mulDivDown(totalAssets(), supply);
}
/**
* @notice Modification of the standard ERC4626 previewWithdraw implementation.
* @dev Since debt is a liability instead of an asset, roundUp and roundDown are inverted compared to the standard implementation.
*/
function previewWithdraw(uint256 assets) public view override returns (uint256) {
// Cache totalSupply.
uint256 supply = totalSupply;
return supply == 0 ? assets : assets.mulDivDown(supply, totalAssets());
}
/*//////////////////////////////////////////////////////////////
TRANSFER LOGIC
//////////////////////////////////////////////////////////////*/
/**
* @notice Modification of the standard ERC4626 transfer implementation.
* @dev No public transfer allowed.
*/
function transfer(address, uint256) public pure override returns (bool) {
revert DebtTokenErrors.FunctionNotImplemented();
}
/**
* @notice Modification of the standard ERC4626 transferFrom implementation.
* @dev No public transferFrom allowed.
* @dev The functions approve() and permit() will not revert, but since transferFrom() reverts,
* it can never be used to transfer tokens.
*/
function transferFrom(address, address, uint256) public pure override returns (bool) {
revert DebtTokenErrors.FunctionNotImplemented();
}
}
// 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: AGPL-3.0-only
pragma solidity >=0.8.0;
import {ERC20} from "../tokens/ERC20.sol";
import {SafeTransferLib} from "../utils/SafeTransferLib.sol";
import {FixedPointMathLib} from "../utils/FixedPointMathLib.sol";
/// @notice Minimal ERC4626 tokenized Vault implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/mixins/ERC4626.sol)
abstract contract ERC4626 is ERC20 {
using SafeTransferLib for ERC20;
using FixedPointMathLib for uint256;
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares);
event Withdraw(
address indexed caller,
address indexed receiver,
address indexed owner,
uint256 assets,
uint256 shares
);
/*//////////////////////////////////////////////////////////////
IMMUTABLES
//////////////////////////////////////////////////////////////*/
ERC20 public immutable asset;
constructor(
ERC20 _asset,
string memory _name,
string memory _symbol
) ERC20(_name, _symbol, _asset.decimals()) {
asset = _asset;
}
/*//////////////////////////////////////////////////////////////
DEPOSIT/WITHDRAWAL LOGIC
//////////////////////////////////////////////////////////////*/
function deposit(uint256 assets, address receiver) public virtual returns (uint256 shares) {
// Check for rounding error since we round down in previewDeposit.
require((shares = previewDeposit(assets)) != 0, "ZERO_SHARES");
// Need to transfer before minting or ERC777s could reenter.
asset.safeTransferFrom(msg.sender, address(this), assets);
_mint(receiver, shares);
emit Deposit(msg.sender, receiver, assets, shares);
afterDeposit(assets, shares);
}
function mint(uint256 shares, address receiver) public virtual returns (uint256 assets) {
assets = previewMint(shares); // No need to check for rounding error, previewMint rounds up.
// Need to transfer before minting or ERC777s could reenter.
asset.safeTransferFrom(msg.sender, address(this), assets);
_mint(receiver, shares);
emit Deposit(msg.sender, receiver, assets, shares);
afterDeposit(assets, shares);
}
function withdraw(
uint256 assets,
address receiver,
address owner
) public virtual returns (uint256 shares) {
shares = previewWithdraw(assets); // No need to check for rounding error, previewWithdraw rounds up.
if (msg.sender != owner) {
uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;
}
beforeWithdraw(assets, shares);
_burn(owner, shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
asset.safeTransfer(receiver, assets);
}
function redeem(
uint256 shares,
address receiver,
address owner
) public virtual returns (uint256 assets) {
if (msg.sender != owner) {
uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;
}
// Check for rounding error since we round down in previewRedeem.
require((assets = previewRedeem(shares)) != 0, "ZERO_ASSETS");
beforeWithdraw(assets, shares);
_burn(owner, shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
asset.safeTransfer(receiver, assets);
}
/*//////////////////////////////////////////////////////////////
ACCOUNTING LOGIC
//////////////////////////////////////////////////////////////*/
function totalAssets() public view virtual returns (uint256);
function convertToShares(uint256 assets) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? assets : assets.mulDivDown(supply, totalAssets());
}
function convertToAssets(uint256 shares) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? shares : shares.mulDivDown(totalAssets(), supply);
}
function previewDeposit(uint256 assets) public view virtual returns (uint256) {
return convertToShares(assets);
}
function previewMint(uint256 shares) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? shares : shares.mulDivUp(totalAssets(), supply);
}
function previewWithdraw(uint256 assets) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? assets : assets.mulDivUp(supply, totalAssets());
}
function previewRedeem(uint256 shares) public view virtual returns (uint256) {
return convertToAssets(shares);
}
/*//////////////////////////////////////////////////////////////
DEPOSIT/WITHDRAWAL LIMIT LOGIC
//////////////////////////////////////////////////////////////*/
function maxDeposit(address) public view virtual returns (uint256) {
return type(uint256).max;
}
function maxMint(address) public view virtual returns (uint256) {
return type(uint256).max;
}
function maxWithdraw(address owner) public view virtual returns (uint256) {
return convertToAssets(balanceOf[owner]);
}
function maxRedeem(address owner) public view virtual returns (uint256) {
return balanceOf[owner];
}
/*//////////////////////////////////////////////////////////////
INTERNAL HOOKS LOGIC
//////////////////////////////////////////////////////////////*/
function beforeWithdraw(uint256 assets, uint256 shares) internal virtual {}
function afterDeposit(uint256 assets, uint256 shares) internal virtual {}
}
/**
* Created by Pragma Labs
* SPDX-License-Identifier: BUSL-1.1
*/
pragma solidity 0.8.22;
library LiquidatorErrors {
// Thrown when the liquidateAccount function is called on an Account that is already in an auction.
error AuctionOngoing();
// Thrown when cutOffTime is above the maximum value.
error CutOffTooHigh();
// Thrown when cutOffTime is below the minimum value.
error CutOffTooLow();
// Thrown if the auction was not successfully ended.
error EndAuctionFailed();
// Thrown when halfLifeTime is above the maximum value.
error HalfLifeTimeTooHigh();
// Thrown when halfLifeTime is below the minimum value.
error HalfLifeTimeTooLow();
// Thrown when the auction has not yet expired.
error InvalidBid();
// Thrown when account specified is not an Arcadia Account.
error IsNotAnAccount();
// Thrown when the start price multiplier is above the maximum value.
error MultiplierTooHigh();
// Thrown when the start price multiplier is below the minimum value.
error MultiplierTooLow();
// Thrown when an Account is not for sale.
error NotForSale();
// Thrown when not authorized.
error NotAuthorized();
// Thrown when the sequencer uptime oracle is not reverting.
error OracleNotReverting();
// Thrown when the sequencer uptime oracle is reverting.
error OracleReverting();
// Thrown when the sequencer is down.
error SequencerDown();
}
library DebtTokenErrors {
// Thrown when function called has not be implemented.
error FunctionNotImplemented();
// Thrown when amount of asset would represent zero shares.
error ZeroShares();
}
library LendingPoolErrors {
// Thrown when amount available to withdraw of an asset is less than amount requested to withdraw.
error AmountExceedsBalance();
// Thrown when an auction is in process.
error AuctionOngoing();
// Thrown when an Account would become unhealthy OR the creditor of the Account is not the specific lending pool OR the Account version would not be valid.
error InvalidVersion();
// Thrown when account specified is not an Arcadia Account.
error IsNotAnAccount();
// Thrown when an account has zero debt.
error IsNotAnAccountWithDebt();
// Thrown when liquidation weights are above maximum value.
error LiquidationWeightsTooHigh();
// Thrown when a specific tranche does not exist.
error NonExistingTranche();
// Thrown when address has an open position
error OpenPositionNonZero();
// Thrown when the tranche of the lending pool already exists.
error TrancheAlreadyExists();
// Thrown when caller is not authorized.
error Unauthorized();
// Thrown when asset amount in input is zero.
error ZeroAmount();
}
library TrancheErrors {
// Thrown when a tranche is locked.
error Locked();
// Thrown when amount of shares would represent zero assets.
error ZeroAssets();
// Thrown when an auction is in process.
error AuctionOngoing();
// Thrown when caller is not valid.
error Unauthorized();
// Thrown when amount of asset would represent zero shares.
error ZeroShares();
}
// 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.
/*//////////////////////////////////////////////////////////////
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))
}
}
/*//////////////////////////////////////////////////////////////
GENERAL NUMBER UTILITIES
//////////////////////////////////////////////////////////////*/
}
/**
* Created by Pragma Labs
* SPDX-License-Identifier: MIT
*/
pragma solidity 0.8.22;
import { AssetValueAndRiskFactors } from "../../lib/accounts-v2/src/libraries/AssetValuationLib.sol";
interface IAccount {
/**
* @notice Returns the address of the owner of the Account.
*/
function owner() external view returns (address);
/**
* @notice Calculates the total collateral value (MTM discounted with a haircut) of the Account.
* @return collateralValue The collateral value, returned in the decimal precision of the Numeraire.
*/
function getCollateralValue() external view returns (uint256);
/**
* @notice Returns the used margin of the Account.
* @return usedMargin The total amount of Margin that is currently in use to back liabilities.
*/
function getUsedMargin() external view returns (uint256);
/**
* @notice Checks if the Account is still healthy for an updated open position.
* @param openPosition The new open position.
* @return accountVersion The current Account version.
*/
function increaseOpenPosition(uint256 openPosition) external returns (uint256);
/**
* @notice Executes a flash action initiated by the Creditor.
* @param actionTarget The contract address of the flashAction.
* @param actionData A bytes object containing three structs and two bytes objects.
* The first struct contains the info about the assets to withdraw from this Account to the actionTarget.
* The second struct contains the info about the owner's assets that need to be transferred from the owner to the actionTarget.
* The third struct contains the permit for the Permit2 transfer.
* The first bytes object contains the signature for the Permit2 transfer.
* The second bytes object contains the encoded input for the actionTarget.
* @return accountVersion The current Account version.
*/
function flashActionByCreditor(bytes calldata callbackData, address actionTarget, bytes calldata actionData)
external
returns (uint256);
/**
* @notice Checks if an Account is liquidatable and continues the liquidation flow.
* @param initiator The address of the liquidation initiator.
* @return assetAddresses Array of the contract addresses of the assets in Account.
* @return assetIds Array of the IDs of the assets in Account.
* @return assetAmounts Array with the amounts of the assets in Account.
* @return creditor_ The contract address of the Creditor.
* @return minimumMargin_ The minimum margin.
* @return openPosition The open position (liabilities) issued against the Account.
* @return assetAndRiskValues Array of asset values and corresponding collateral and liquidation factors.
*/
function startLiquidation(address initiator)
external
returns (
address[] memory,
uint256[] memory,
uint256[] memory,
address,
uint96,
uint256,
AssetValueAndRiskFactors[] memory
);
/**
* @notice Transfers the asset bought by a bidder during a liquidation event.
* @param assetAddresses Array of the contract addresses of the assets.
* @param assetIds Array of the IDs of the assets.
* @param assetAmounts Array with the amounts of the assets.
* @param bidder The address of the bidder.
* @return assetAmounts_ Array with the actual transferred amounts of assets.
*/
function auctionBid(
address[] memory assetAddresses,
uint256[] memory assetIds,
uint256[] memory assetAmounts,
address bidder
) external returns (uint256[] memory);
/**
* @notice Transfers all assets of the Account in case the auction did not end successful (= Bought In).
* @param to The recipient's address to receive the assets, set by the Creditor.
*/
function auctionBoughtIn(address to) external;
/**
* @notice Sets the "inAuction" flag to false when an auction ends.
*/
function endAuction() external;
}
/**
* Created by Pragma Labs
* SPDX-License-Identifier: MIT
*/
pragma solidity 0.8.22;
/**
* @title Creditor implementation.
* @author Pragma Labs
* @notice This contract contains the minimum functionality a Creditor, interacting with Arcadia Accounts, needs to implement.
* @dev For the implementation of Arcadia Accounts, see: https://github.com/arcadia-finance/accounts-v2.
*/
interface ICreditor {
/**
* @notice Checks if Account fulfills all requirements and returns Creditor parameters.
* @param accountVersion The version of the Arcadia Account.
* @return success Bool indicating if all requirements are met.
* @return numeraire The Numeraire of the Creditor.
* @return liquidator The liquidator of the Creditor.
* @return minimumMargin The minimum amount of collateral that must be held in the Account before a position can be opened,
* denominated in the numeraire.
*/
function openMarginAccount(uint256 accountVersion) external returns (bool, address, address, uint256);
/**
* @notice Checks if Account can be closed.
* @param account The Account address.
*/
function closeMarginAccount(address account) external;
/**
* @notice Returns the open position of the Account.
* @param account The Account address.
* @return openPosition The open position of the Account.
*/
function getOpenPosition(address account) external view returns (uint256);
/**
* @notice Returns the Risk Manager of the creditor.
* @return riskManager The Risk Manager of the creditor.
*/
function riskManager() external view returns (address riskManager);
/**
* @notice Callback of the Account during a flashAction.
* @param callbackData The data for the actions that have to be executed by the Creditor during a flashAction.
*/
function flashActionCallback(bytes calldata callbackData) external;
/**
* @notice Starts the liquidation of an account and returns the open position of the Account.
* @param initiator The address of the liquidation initiator.
* @param minimumMargin The minimum margin of the Account.
* @return openPosition the open position of the Account.
*/
function startLiquidation(address initiator, uint256 minimumMargin) external returns (uint256);
}
/**
* Created by Pragma Labs
* SPDX-License-Identifier: MIT
*/
pragma solidity 0.8.22;
interface IFactory {
/**
* @notice View function returning if an address is a Account.
* @param account The address to be checked.
* @return bool Whether the address is a Account or not.
*/
function isAccount(address account) external view returns (bool);
/**
* @notice Returns the owner of a Account.
* @param account The Account address.
* @return owner The Account owner.
*/
function ownerOfAccount(address account) external view returns (address);
/**
* @notice Function used to transfer a Account between users.
* @param from The sender.
* @param to The target.
* @param account The address of the Account that is transferred.
*/
function safeTransferFrom(address from, address to, address account) external;
}
/**
* Created by Pragma Labs
* SPDX-License-Identifier: MIT
*/
pragma solidity 0.8.22;
interface ILendingPool {
/**
* @notice Returns the total redeemable amount of liquidity in the underlying asset.
* @return totalLiquidity The total redeemable amount of liquidity in the underlying asset.
*/
function totalLiquidity() external view returns (uint256);
/**
* @notice Deposit assets in the Lending Pool.
* @param assets The amount of assets of the underlying ERC-20 token being deposited.
* @param from The address of the Liquidity Provider who deposits the underlying ERC-20 token via a Tranche.
*/
function depositInLendingPool(uint256 assets, address from) external;
/**
* @notice Withdraw assets from the Lending Pool.
* @param assets The amount of assets of the underlying ERC-20 tokens being withdrawn.
* @param receiver The address of the receiver of the underlying ERC-20 tokens.
*/
function withdrawFromLendingPool(uint256 assets, address receiver) external;
/**
* @notice Returns the redeemable amount of liquidity in the underlying asset of an address.
* @param owner The address of the liquidity provider.
* @return assets The redeemable amount of liquidity in the underlying asset.
*/
function liquidityOf(address owner) external view returns (uint256);
/**
* @notice liquidityOf, but syncs the unrealised interest first.
* @param owner The address of the liquidity provider.
* @return assets The redeemable amount of liquidity in the underlying asset.
*/
function liquidityOfAndSync(address owner) external returns (uint256);
/**
* @notice Repays debt via an auction.
* @param startDebt The amount of debt of the Account the moment the liquidation was initiated.
* @param minimumMargin The minimum margin of the Account.
* @param minimumMargin The minimum margin of the Account.
* @param amount The amount of debt repaid by a bidder during the auction.
* @param account The contract address of the Arcadia Account backing the loan.
* @param bidder The address of the bidder.
* @return earlyTerminate Bool indicating of the full amount of debt was repaid.
*/
function auctionRepay(uint256 startDebt, uint256 minimumMargin, uint256 amount, address account, address bidder)
external
returns (bool);
/**
* @notice Settles the liquidation process for a specific Account.
* @param account The address of the Account undergoing liquidation settlement.
* @param startDebt The initial debt amount of the liquidated Account.
* @param minimumMargin The minimum margin of the Account.
* @param terminator The address of the liquidation terminator.
*/
function settleLiquidationHappyFlow(address account, uint256 startDebt, uint256 minimumMargin, address terminator)
external;
/**
* @notice Settles the liquidation process for a specific Account.
* @param account The address of the Account undergoing liquidation settlement.
* @param startDebt The initial debt amount of the liquidated Account.
* @param minimumMargin The minimum margin of the Account.
* @param terminator The address of the liquidation terminator.
*/
function settleLiquidationUnhappyFlow(address account, uint256 startDebt, uint256 minimumMargin, address terminator)
external;
}
/**
* Created by Pragma Labs
* SPDX-License-Identifier: MIT
*/
pragma solidity 0.8.22;
interface ITranche {
/**
* @notice Locks the tranche in case all liquidity of the tranche is written of due to bad debt.
*/
function lock() external;
/**
* @notice Locks the tranche while an auction is in progress.
* @param auctionInProgress Flag indicating if there are auctions in progress.
*/
function setAuctionInProgress(bool auctionInProgress) external;
}
/**
* Created by Pragma Labs
* SPDX-License-Identifier: BUSL-1.1
*/
pragma solidity 0.8.22;
import { Creditor } from "../lib/accounts-v2/src/abstracts/Creditor.sol";
import { DebtToken, ERC20, ERC4626 } from "./DebtToken.sol";
import { FixedPointMathLib } from "../lib/solmate/src/utils/FixedPointMathLib.sol";
import { IAccount } from "./interfaces/IAccount.sol";
import { IFactory } from "./interfaces/IFactory.sol";
import { ILendingPool } from "./interfaces/ILendingPool.sol";
import { ITranche } from "./interfaces/ITranche.sol";
import { LendingPoolErrors } from "./libraries/Errors.sol";
import { LendingPoolGuardian } from "./guardians/LendingPoolGuardian.sol";
import { LogExpMath } from "./libraries/LogExpMath.sol";
import { SafeCastLib } from "../lib/solmate/src/utils/SafeCastLib.sol";
import { SafeTransferLib } from "../lib/solmate/src/utils/SafeTransferLib.sol";
/**
* @title Arcadia LendingPool.
* @author Pragma Labs
* @notice The Lending pool is responsible for the:
* - Accounting of the liabilities of borrowers via the debtTokens (ERC4626).
* - Accounting of the liquidity of the Liquidity Providers, via one or more Tranche(s) (ERC4626).
* - Management of issuing and repaying debt.
* - Management of interest payments.
* - Settlement of liquidations and default events.
*/
contract LendingPool is LendingPoolGuardian, Creditor, DebtToken, ILendingPool {
using FixedPointMathLib for uint256;
using SafeTransferLib for ERC20;
/* //////////////////////////////////////////////////////////////
CONSTANTS
////////////////////////////////////////////////////////////// */
// Seconds per year, leap years ignored.
uint256 internal constant YEARLY_SECONDS = 31_536_000;
// Contract address of the Arcadia Account Factory.
address internal immutable ACCOUNT_FACTORY;
// Contract address of the Liquidator contract.
address internal immutable LIQUIDATOR;
// The unit for fixed point numbers with 4 decimals precision.
uint256 internal constant ONE_4 = 10_000;
// Maximum total liquidation penalty, 4 decimal precision.
uint256 internal constant MAX_TOTAL_PENALTY = 1100;
/* //////////////////////////////////////////////////////////////
STORAGE
////////////////////////////////////////////////////////////// */
// The current interest rate, 18 decimals precision.
uint80 public interestRate;
// The interest rate when utilisation is 0.
// 18 decimals precision.
uint72 internal baseRatePerYear;
// The slope of the first curve, defined as the delta in interest rate for a delta in utilisation of 100%.
// 18 decimals precision.
uint72 internal lowSlopePerYear;
// The slope of the second curve, defined as the delta in interest rate for a delta in utilisation of 100%.
// 18 decimals precision.
uint72 internal highSlopePerYear;
// The optimal capital utilisation, where we go from the first curve to the steeper second curve.
// 4 decimal precision.
uint16 internal utilisationThreshold;
// Last timestamp that interests were realized.
uint32 internal lastSyncedTimestamp;
// Fee issued upon taking debt, 4 decimals precision (10 equals 0.001 or 0.1%), capped at 255 (2.55%).
uint8 public originationFee;
// Sum of all the interest weights of the tranches + treasury.
uint24 internal totalInterestWeight;
// Fraction (interestWeightTreasury / totalInterestWeight) of the interest fees that go to the treasury.
uint16 internal interestWeightTreasury;
// Fraction (liquidationWeightTreasury / totalLiquidationWeight) of the liquidation fees that goes to the treasury.
uint16 internal liquidationWeightTreasury;
// Fraction (liquidationWeightTranche / totalLiquidationWeight) of the liquidation fees that goes to the most Junior Tranche.
uint16 internal liquidationWeightTranche;
// Total amount of `underlying asset` that is claimable by the LPs. Does not take into account pending interests.
uint128 internal totalRealisedLiquidity;
// The minimum amount of collateral that must be held in an Account before a position can be opened.
uint96 internal minimumMargin;
// Address of the protocol treasury.
address internal treasury;
// Number of auctions that are currently in progress.
uint16 internal auctionsInProgress;
// Maximum amount of `underlying asset` that is paid as fee to the initiator/terminator of a liquidation.
uint80 internal maxReward;
// Minimum initiation and termination reward, relative to the minimumMargin, 4 decimal precision.
uint16 internal minRewardWeight;
// Fee paid to the Liquidation Initiator.
// Defined as a fraction of the openDebt with 4 decimals precision.
// Absolute fee can be further capped to a max amount by the creditor.
uint16 internal initiationWeight;
// Penalty the Account owner has to pay to the Creditor on top of the open Debt for being liquidated.
// Defined as a fraction of the openDebt with 4 decimals precision.
uint16 internal penaltyWeight;
// Fee paid to the address that is ending an auction.
// Defined as a fraction of the openDebt with 4 decimals precision.
uint16 internal terminationWeight;
// Array of the interest weights of each Tranche.
// Fraction (interestWeightTranches[i] / totalInterestWeight) of the interest fees that go to Tranche i.
uint16[] internal interestWeightTranches;
// Array of the contract addresses of the Tranches.
address[] internal tranches;
// Map tranche => status.
mapping(address => bool) internal isTranche;
// Map tranche => interestWeight.
// Fraction (interestWeightTranches[i] / totalInterestWeight) of the interest fees that go to Tranche i.
mapping(address => uint256) internal interestWeight;
// Map tranche => realisedLiquidity.
// Amount of `underlying asset` that is claimable by the liquidity providers.
// Does not take into account pending interests.
mapping(address => uint256) internal realisedLiquidityOf;
// Map Account => owner => beneficiary => amount.
// Stores the credit allowances for a beneficiary per Account and per Owner.
mapping(address => mapping(address => mapping(address => uint256))) public creditAllowance;
/* //////////////////////////////////////////////////////////////
EVENTS
////////////////////////////////////////////////////////////// */
event AuctionStarted(address indexed account, address indexed creditor, uint128 openDebt);
event AuctionFinished(
address indexed account,
address indexed creditor,
uint256 startDebt,
uint256 initiationReward,
uint256 terminationReward,
uint256 penalty,
uint256 badDebt,
uint256 surplus
);
event Borrow(
address indexed account, address indexed by, address to, uint256 amount, uint256 fee, bytes3 indexed referrer
);
event CreditApproval(address indexed account, address indexed owner, address indexed beneficiary, uint256 amount);
event InterestSynced(uint256 interest);
event InterestWeightTrancheUpdated(address indexed tranche, uint8 indexed trancheIndex, uint16 interestWeight);
event LiquidationWeightTrancheUpdated(uint16 liquidationWeight);
event PoolStateUpdated(uint256 totalDebt, uint256 totalLiquidity, uint80 interestRate);
event Repay(address indexed account, address indexed from, uint256 amount);
event TranchePopped(address tranche);
event TreasuryWeightsUpdated(uint16 interestWeight, uint16 liquidationWeight);
/* //////////////////////////////////////////////////////////////
MODIFIERS
////////////////////////////////////////////////////////////// */
/**
* @notice Checks if caller is the Liquidator.
*/
modifier onlyLiquidator() {
if (LIQUIDATOR != msg.sender) revert LendingPoolErrors.Unauthorized();
_;
}
/**
* @notice Checks if caller is a Tranche.
*/
modifier onlyTranche() {
if (!isTranche[msg.sender]) revert LendingPoolErrors.Unauthorized();
_;
}
/**
* @notice Syncs interest to LPs and treasury and updates the interest rate.
*/
modifier processInterests() {
_syncInterests();
_;
// _updateInterestRate() modifies the state (effect), but can safely be called after interactions.
// Cannot be exploited by re-entrancy attack.
_updateInterestRate(realisedDebt, totalRealisedLiquidity);
}
/* //////////////////////////////////////////////////////////////
CONSTRUCTOR
////////////////////////////////////////////////////////////// */
/**
* @notice The constructor for a lending pool.
* @param riskManager_ The address of the new Risk Manager.
* @param asset_ The underlying ERC20 token of the Lending Pool.
* @param treasury_ The address of the protocol treasury.
* @param accountFactory The contract address of the Arcadia Account Factory.
* @param liquidator The contract address of the Liquidator.
* @dev The name and symbol of the DebtToken are automatically generated, based on the name and symbol of the underlying token.
*/
constructor(address riskManager_, ERC20 asset_, address treasury_, address accountFactory, address liquidator)
LendingPoolGuardian()
Creditor(riskManager_)
DebtToken(asset_)
{
treasury = treasury_;
ACCOUNT_FACTORY = accountFactory;
LIQUIDATOR = liquidator;
}
/* //////////////////////////////////////////////////////////////
TRANCHES LOGIC
////////////////////////////////////////////////////////////// */
/**
* @notice Adds a tranche to the Lending Pool.
* @param tranche The address of the Tranche.
* @param interestWeight_ The interest weight of the specific Tranche.
* @dev The order of the tranches is important, the most senior tranche is added first at index 0, the most junior at the last index.
* @dev Each Tranche is an ERC4626 contract.
* @dev The interest weight of each Tranche determines the relative share of the yield (interest payments) that goes to its Liquidity providers.
*/
function addTranche(address tranche, uint16 interestWeight_) external onlyOwner processInterests {
if (auctionsInProgress > 0) revert LendingPoolErrors.AuctionOngoing();
if (isTranche[tranche]) revert LendingPoolErrors.TrancheAlreadyExists();
totalInterestWeight += interestWeight_;
interestWeightTranches.push(interestWeight_);
interestWeight[tranche] = interestWeight_;
uint8 trancheIndex = uint8(tranches.length);
tranches.push(tranche);
isTranche[tranche] = true;
emit InterestWeightTrancheUpdated(tranche, trancheIndex, interestWeight_);
}
/**
* @notice Changes the interest weight of a specific Tranche.
* @param index The index of the Tranche for which a new interest weight is being set.
* @param interestWeight_ The new interest weight of the Tranche at the index.
* @dev The interest weight of each Tranche determines the relative share of yield (interest payments) that goes to its Liquidity providers.
*/
function setInterestWeightTranche(uint256 index, uint16 interestWeight_) external onlyOwner processInterests {
if (index >= tranches.length) revert LendingPoolErrors.NonExistingTranche();
totalInterestWeight = totalInterestWeight - interestWeightTranches[index] + interestWeight_;
interestWeightTranches[index] = interestWeight_;
address tranche = tranches[index];
interestWeight[tranche] = interestWeight_;
emit InterestWeightTrancheUpdated(tranche, uint8(index), interestWeight_);
}
/**
* @notice Changes the liquidation weight of the most Junior Tranche.
* @param liquidationWeight The new liquidation weight of the Tranche at the highest index.
* @dev The liquidation weight determines the relative share of liquidation fees that goes to the most Junior Tranche.
*/
function setLiquidationWeightTranche(uint16 liquidationWeight) external onlyOwner {
emit LiquidationWeightTrancheUpdated(liquidationWeightTranche = liquidationWeight);
}
/**
* @notice Removes the Tranche at the last index (most junior).
* @param index The index of the last Tranche.
* @param tranche The address of the last Tranche.
* @dev This function can only be called by the function _processDefault(uint256 assets),
* when there is a default as big as (or bigger than) the complete amount of liquidity of the most junior Tranche.
* @dev Passing the input parameters to the function saves gas compared to reading the address and index of the last Tranche from storage.
* No need to check if index and Tranche are indeed of the last tranche since function is only called by _processDefault.
*/
function _popTranche(uint256 index, address tranche) internal {
unchecked {
totalInterestWeight -= interestWeightTranches[index];
}
isTranche[tranche] = false;
interestWeightTranches.pop();
tranches.pop();
interestWeight[tranche] = 0;
emit TranchePopped(tranche);
}
/* ///////////////////////////////////////////////////////////////
TREASURY FEE CONFIGURATION
////////////////////////////////////////////////////////////// */
/**
* @notice Changes the interest and liquidation weight of the Treasury.
* @param interestWeight_ The new interestWeight of the treasury.
* @param liquidationWeight The new liquidationWeight of the treasury.
* @dev The interestWeight determines the relative share of the yield (interest payments) that goes to the protocol treasury.
* @dev Setting interestWeightTreasury to a very high value will cause the treasury to collect all interest fees from that moment on.
* Although this will affect the future profits of liquidity providers, no funds nor realized interest are at risk for LPs.
*/
function setTreasuryWeights(uint16 interestWeight_, uint16 liquidationWeight) external onlyOwner processInterests {
totalInterestWeight = totalInterestWeight - interestWeightTreasury + interestWeight_;
interestWeight[treasury] = interestWeight_;
emit TreasuryWeightsUpdated(
interestWeightTreasury = interestWeight_, liquidationWeightTreasury = liquidationWeight
);
}
/**
* @notice Sets new treasury address.
* @param treasury_ The new address of the treasury.
*/
function setTreasury(address treasury_) external onlyOwner {
treasury = treasury_;
}
/**
* @notice Sets the new origination fee.
* @param originationFee_ The new origination fee.
* @dev originationFee is limited by being a uint8 -> max value is 2.55%
* 4 decimal precision (10 = 0.1%).
*/
function setOriginationFee(uint8 originationFee_) external onlyOwner {
originationFee = originationFee_;
}
/* //////////////////////////////////////////////////////////////
DEPOSIT/WITHDRAWAL LOGIC
////////////////////////////////////////////////////////////// */
/**
* @notice Deposit assets in the Lending Pool.
* @param assets The amount of assets of the underlying ERC20 tokens being deposited.
* @param from The address of the Liquidity Provider who deposits the underlying ERC20 token via a Tranche.
* @dev This function can only be called by Tranches.
*/
function depositInLendingPool(uint256 assets, address from)
external
whenDepositNotPaused
onlyTranche
processInterests
{
// Need to transfer before minting or ERC777s could reenter.
// Address(this) is trusted -> no risk on re-entrancy attack after transfer.
asset.safeTransferFrom(from, address(this), assets);
unchecked {
realisedLiquidityOf[msg.sender] += assets;
totalRealisedLiquidity = SafeCastLib.safeCastTo128(assets + totalRealisedLiquidity);
}
}
/**
* @notice Donate assets to the Lending Pool.
* @param trancheIndex The index of the tranche to donate to.
* @param assets The amount of assets of the underlying ERC20 tokens being deposited.
* @dev Can be used by anyone to donate assets to the Lending Pool.
* It is supposed to serve as a way to compensate the jrTranche after an
* auction didn't get sold and was manually liquidated after cutoffTime.
* @dev Inflation attacks by the first depositor in the Tranches have to be prevented with virtual assets/shares.
*/
function donateToTranche(uint256 trancheIndex, uint256 assets) external whenDepositNotPaused processInterests {
if (assets == 0) revert LendingPoolErrors.ZeroAmount();
address tranche = tranches[trancheIndex];
// Need to transfer before donating or ERC777s could reenter.
// Address(this) is trusted -> no risk on re-entrancy attack after transfer.
asset.safeTransferFrom(msg.sender, address(this), assets);
unchecked {
realisedLiquidityOf[tranche] += assets; //[̲̅$̲̅(̲̅ ͡° ͜ʖ ͡°̲̅)̲̅$̲̅]
totalRealisedLiquidity = SafeCastLib.safeCastTo128(assets + totalRealisedLiquidity);
}
}
/**
* @notice Withdraw assets from the Lending Pool.
* @param assets The amount of assets of the underlying ERC20 tokens being withdrawn.
* @param receiver The address of the receiver of the underlying ERC20 tokens.
* @dev This function can be called by anyone with an open balance (realisedLiquidityOf[address] bigger than 0),
* which can be both Tranches as other address (treasury, Liquidation Initiators, Liquidated Account Owner...).
*/
function withdrawFromLendingPool(uint256 assets, address receiver)
external
whenWithdrawNotPaused
processInterests
{
if (realisedLiquidityOf[msg.sender] < assets) revert LendingPoolErrors.AmountExceedsBalance();
unchecked {
realisedLiquidityOf[msg.sender] -= assets;
totalRealisedLiquidity = SafeCastLib.safeCastTo128(totalRealisedLiquidity - assets);
}
asset.safeTransfer(receiver, assets);
}
/* //////////////////////////////////////////////////////////////
LENDING LOGIC
////////////////////////////////////////////////////////////// */
/**
* @notice Approve a beneficiary to take out debt against an Arcadia Account.
* @param beneficiary The address of the beneficiary who can take out debt backed by an Arcadia Account.
* @param amount The amount of underlying ERC20 tokens to be lent out.
* @param account The address of the Arcadia Account backing the debt.
*/
function approveBeneficiary(address beneficiary, uint256 amount, address account) external {
// If Account is not an actual address of an Arcadia Account, ownerOfAccount(address) will return the zero address.
if (IFactory(ACCOUNT_FACTORY).ownerOfAccount(account) != msg.sender) revert LendingPoolErrors.Unauthorized();
creditAllowance[account][msg.sender][beneficiary] = amount;
emit CreditApproval(account, msg.sender, beneficiary, amount);
}
/**
* @notice Takes out debt backed by collateral in an Arcadia Account.
* @param amount The amount of underlying ERC20 tokens to be lent out.
* @param account The address of the Arcadia Account backing the debt.
* @param to The address who receives the lent out underlying tokens.
* @param referrer A unique identifier of the referrer, who will receive part of the fees generated by this transaction.
* @dev The sender might be different than the owner if they have the proper allowances.
*/
function borrow(uint256 amount, address account, address to, bytes3 referrer)
external
whenBorrowNotPaused
processInterests
{
if (amount == 0) revert LendingPoolErrors.ZeroAmount();
// If Account is not an actual address of an Account, ownerOfAccount(address) will return the zero address.
address accountOwner = IFactory(ACCOUNT_FACTORY).ownerOfAccount(account);
if (accountOwner == address(0)) revert LendingPoolErrors.IsNotAnAccount();
uint256 amountWithFee = amount + amount.mulDivUp(originationFee, ONE_4);
// Check allowances to take debt.
if (accountOwner != msg.sender) {
uint256 allowed = creditAllowance[account][accountOwner][msg.sender];
if (allowed != type(uint256).max) {
creditAllowance[account][accountOwner][msg.sender] = allowed - amountWithFee;
}
}
// Mint debt tokens to the Account.
_deposit(amountWithFee, account);
// Add origination fee to the treasury.
unchecked {
if (amountWithFee - amount > 0) {
totalRealisedLiquidity = SafeCastLib.safeCastTo128(amountWithFee + totalRealisedLiquidity - amount);
realisedLiquidityOf[treasury] += amountWithFee - amount;
}
}
// UpdateOpenPosition checks that the Account indeed has opened a margin account for this Lending Pool and
// checks that it is still healthy after the debt is increased with amountWithFee.
// Reverts in Account if one of the checks fails.
uint256 accountVersion = IAccount(account).increaseOpenPosition(maxWithdraw(account));
if (!isValidVersion[accountVersion]) revert LendingPoolErrors.InvalidVersion();
// Transfer fails if there is insufficient liquidity in the pool.
asset.safeTransfer(to, amount);
emit Borrow(account, msg.sender, to, amount, amountWithFee - amount, referrer);
}
/**
* @notice Repays debt.
* @param amount The amount of underlying ERC20 tokens to be repaid.
* @param account The contract address of the Arcadia Account backing the debt.
* @dev if Account is not an actual address of an Arcadia Account, maxWithdraw(account) will always return 0.
* Function will not revert, but amount is always 0.
* @dev Anyone (EOAs and contracts) can repay debt in the name of an Account.
*/
function repay(uint256 amount, address account) external whenRepayNotPaused processInterests {
uint256 accountDebt = maxWithdraw(account);
amount = accountDebt > amount ? amount : accountDebt;
// Need to transfer before burning debt or ERC777s could reenter.
// Address(this) is trusted -> no risk on re-entrancy attack after transfer.
asset.safeTransferFrom(msg.sender, address(this), amount);
_withdraw(amount, address(this), account);
emit Repay(account, msg.sender, amount);
}
/**
* @notice Repays debt via an auction.
* @param startDebt The amount of debt of the Account the moment the liquidation was initiated.
* @param minimumMargin_ The minimum margin of the Account.
* @param amount The amount repaid by a bidder during the auction.
* @param account The contract address of the Arcadia Account backing the debt.
* @param bidder The address of the bidder.
* @return earlyTerminate Bool indicating whether the full amount of debt was repaid.
* @dev This function allows a liquidator to repay a specified amount of debt for a user.
*/
function auctionRepay(uint256 startDebt, uint256 minimumMargin_, uint256 amount, address account, address bidder)
external
whenLiquidationNotPaused
onlyLiquidator
processInterests
returns (bool earlyTerminate)
{
// Need to transfer before burning debt or ERC777s could reenter.
// Address(this) is trusted -> no risk on re-entrancy attack after transfer.
asset.safeTransferFrom(bidder, address(this), amount);
uint256 accountDebt = maxWithdraw(account);
if (accountDebt == 0) revert LendingPoolErrors.IsNotAnAccountWithDebt();
if (accountDebt <= amount) {
// The amount recovered by selling assets during the auction is bigger than the total debt of the Account.
// -> Terminate the auction and make the surplus available to the Account-Owner.
earlyTerminate = true;
unchecked {
_settleLiquidationHappyFlow(account, startDebt, minimumMargin_, bidder, (amount - accountDebt));
}
amount = accountDebt;
}
_withdraw(amount, address(this), account);
emit Repay(account, bidder, amount);
}
/* //////////////////////////////////////////////////////////////
LEVERAGED ACTIONS LOGIC
////////////////////////////////////////////////////////////// */
/**
* @notice Execute and interact with external logic on leverage.
* @param amountBorrowed The amount of underlying ERC20 tokens to be lent out.
* @param account The address of the Arcadia Account backing the debt.
* @param actionTarget The address of the Action Target to call.
* @param actionData A bytes object containing three actionAssetData structs, an address array and a bytes array.
* @param referrer A unique identifier of the referrer, who will receive part of the fees generated by this transaction.
* @dev The sender might be different than the owner if they have the proper allowances.
* @dev accountManagementAction() works similar to flash loans, this function optimistically calls external logic and checks for the Account state at the very end.
*/
function flashAction(
uint256 amountBorrowed,
address account,
address actionTarget,
bytes calldata actionData,
bytes3 referrer
) external whenBorrowNotPaused processInterests {
// If Account is not an actual address of a Account, ownerOfAccount(address) will return the zero address.
address accountOwner = IFactory(ACCOUNT_FACTORY).ownerOfAccount(account);
if (accountOwner == address(0)) revert LendingPoolErrors.IsNotAnAccount();
// Check allowances to take debt.
if (accountOwner != msg.sender) {
// Calling flashActionByCreditor() gives the sender full control over all assets in the Account,
// Only Beneficiaries with maximum allowance can call the flashAction function.
if (creditAllowance[account][accountOwner][msg.sender] != type(uint256).max) {
revert LendingPoolErrors.Unauthorized();
}
}
// Prepare callback of the Account.
// The borrowing of funds during a flashAction has to be executed via a callback of the Account.
// As such the Account cannot be reentered in between the time that funds are borrowed and the final health check is done.
callbackAccount = account;
bytes memory callbackData = abi.encode(amountBorrowed, actionTarget, msg.sender, referrer);
// The Action Target will use the borrowed funds (optionally with additional assets withdrawn from the Account)
// to execute one or more actions (swap, deposit, mint...).
// Next the action Target will deposit any of the remaining funds or any of the recipient token
// resulting from the actions back into the Account.
// As last step, after all assets are deposited back into the Account a final health check is done:
// The Collateral Value of all assets in the Account must be bigger than the total liabilities against the Account (including the debt taken during this function).
// flashActionByCreditor also checks that the Account indeed has opened a margin account for this Lending Pool.
{
uint256 accountVersion = IAccount(account).flashActionByCreditor(callbackData, actionTarget, actionData);
if (!isValidVersion[accountVersion]) revert LendingPoolErrors.InvalidVersion();
}
}
/**
* @notice Callback of the Account during a flashAction.
* @param account The contract address of the Arcadia Account.
* @param callbackData The data for the borrow during a flashAction.
* @dev During the callback, the Account cannot be reentered and the actionTimestamp is updated.
*/
function _flashActionCallback(address account, bytes calldata callbackData) internal override {
(uint256 amountBorrowed, address actionTarget, address sender, bytes3 referrer) =
abi.decode(callbackData, (uint256, address, address, bytes3));
uint256 amountBorrowedWithFee = amountBorrowed + amountBorrowed.mulDivUp(originationFee, ONE_4);
// Mint debt tokens to the Account.
_deposit(amountBorrowedWithFee, account);
// Add origination fee to the treasury.
unchecked {
if (amountBorrowedWithFee - amountBorrowed > 0) {
totalRealisedLiquidity += SafeCastLib.safeCastTo128(amountBorrowedWithFee - amountBorrowed);
realisedLiquidityOf[treasury] += amountBorrowedWithFee - amountBorrowed;
}
}
// Send Borrowed funds to the actionTarget.
// Transfer fails if there is insufficient liquidity in the pool.
asset.safeTransfer(actionTarget, amountBorrowed);
unchecked {
emit Borrow(account, sender, actionTarget, amountBorrowed, amountBorrowedWithFee - amountBorrowed, referrer);
}
}
/* //////////////////////////////////////////////////////////////
ACCOUNTING LOGIC
////////////////////////////////////////////////////////////// */
/**
* @notice Returns the total amount of outstanding debt in the underlying asset.
* @return totalDebt The total debt in underlying assets.
*/
function totalAssets() public view override returns (uint256 totalDebt) {
// Avoid a second calculation of unrealised debt (expensive)
// if interests are already synced this block.
if (lastSyncedTimestamp != uint32(block.timestamp)) {
totalDebt = realisedDebt + calcUnrealisedDebt();
} else {
totalDebt = realisedDebt;
}
}
/**
* @notice Returns the total redeemable amount of liquidity in the underlying asset.
* @return totalLiquidity_ The total redeemable amount of liquidity in the underlying asset.
*/
function totalLiquidity() external view returns (uint256 totalLiquidity_) {
// Avoid a second calculation of unrealised debt (expensive)
// if interests are already synced this block.
if (lastSyncedTimestamp != uint32(block.timestamp)) {
// The total liquidity equals the sum of the realised liquidity, and the pending interests.
unchecked {
totalLiquidity_ = totalRealisedLiquidity + calcUnrealisedDebt();
}
} else {
totalLiquidity_ = totalRealisedLiquidity;
}
}
/**
* @notice Returns the redeemable amount of liquidity in the underlying asset of an address.
* @param owner_ The address of the liquidity provider.
* @return assets The redeemable amount of liquidity in the underlying asset.
* @dev This function syncs the interests to prevent calculating UnrealisedDebt twice when depositing/withdrawing through the Tranches.
* @dev After calling this function, the interest rate will not be updated until the next processInterests() call.
*/
function liquidityOfAndSync(address owner_) external returns (uint256 assets) {
_syncInterests();
assets = realisedLiquidityOf[owner_];
}
/**
* @notice Returns the redeemable amount of liquidity in the underlying asset of an address.
* @param owner_ The address of the liquidity provider.
* @return assets The redeemable amount of liquidity in the underlying asset.
* ¨* @dev liquidityOf() might deviate from the actual liquidityOf after interests are synced in the following situations:
* - liquidityOf() the treasury will be slightly underestimated, since all rounding errors of all tranches will go to the treasury.
* - Tranches with 0 liquidity don't earn any interests, interests will go to treasury instead.
* - If totalInterestWeight is 0 (will never happen) all interests will go to treasury instead.
*/
function liquidityOf(address owner_) external view returns (uint256 assets) {
// Avoid a second calculation of unrealised debt (expensive).
// if interests are already synced this block.
if (lastSyncedTimestamp != uint32(block.timestamp)) {
// The total liquidity of a tranche equals the sum of the realised liquidity
// of the tranche, and its pending interests.
uint256 interest = calcUnrealisedDebt().mulDivDown(interestWeight[owner_], totalInterestWeight);
unchecked {
assets = realisedLiquidityOf[owner_] + interest;
}
} else {
assets = realisedLiquidityOf[owner_];
}
}
/**
* @notice Skims any surplus funds in the LendingPool to the treasury.
* @dev In normal conditions (when there are no ongoing auctions), the total Claimable Liquidity should be equal
* to the sum of the available funds (the balanceOf() the underlying asset) in the pool and the total open debt.
* In practice the actual sum of available funds and total open debt will always be bigger than the total Claimable Liquidity.
* This because of the rounding errors of the ERC4626 calculations (conversions between assets and shares),
* or because someone accidentally sent funds directly to the pool instead of depositing via a Tranche.
* This functions makes the surplus available to the Treasury (otherwise they would be lost forever).
* @dev In case you accidentally sent funds to the pool, contact the current treasury manager.
*/
function skim() external processInterests {
// During auction initiation, debt tokens representing the liquidation incentives are minted at start of the auction
// yet not accounted for in the totalRealisedLiquidity.
// -> skim function must be blocked during auctions.
if (auctionsInProgress != 0) revert LendingPoolErrors.AuctionOngoing();
// Pending interests are synced via the processInterests modifier.
uint256 delta = asset.balanceOf(address(this)) + realisedDebt - totalRealisedLiquidity;
// Add difference to the treasury.
unchecked {
totalRealisedLiquidity = SafeCastLib.safeCastTo128(delta + totalRealisedLiquidity);
realisedLiquidityOf[treasury] += delta;
}
}
/* //////////////////////////////////////////////////////////////
INTERESTS LOGIC
////////////////////////////////////////////////////////////// */
/**
* @notice Syncs all unrealised debt (= interest for LP and treasury).
* @dev Calculates the unrealised debt since last sync, and realises it by minting an equal amount of
* debt tokens to all debt holders and interests to LPs and the treasury.
*/
function _syncInterests() internal {
// Only Sync interests once per block.
if (lastSyncedTimestamp != uint32(block.timestamp)) {
uint256 unrealisedDebt = calcUnrealisedDebt();
lastSyncedTimestamp = uint32(block.timestamp);
// Sync interests for borrowers.
unchecked {
realisedDebt += unrealisedDebt;
}
// Sync interests for LPs and Protocol Treasury.
_syncInterestsToLiquidityProviders(unrealisedDebt);
emit InterestSynced(unrealisedDebt);
}
}
/**
* @notice Calculates the unrealised debt (interests).
* @return unrealisedDebt The unrealised debt.
* @dev To calculate the unrealised debt over an amount of time, you need to calculate D[(1+r)^x-1].
* The base of the exponential: 1 + r, is a 18 decimals fixed point number
* with r the yearly interest rate.
* The exponent of the exponential: x, is a 18 decimals fixed point number.
* The exponent x is calculated as: the amount of seconds passed since last sync timestamp divided by
* the average of seconds per year.
*/
function calcUnrealisedDebt() public view returns (uint256 unrealisedDebt) {
unchecked {
//gas: Can't overflow for reasonable interest rates.
uint256 base = 1e18 + interestRate;
// gas: Only overflows when (block.timestamp - lastSyncedBlockTimestamp) > 1e59
// in practice: exponent in LogExpMath lib is limited to 130e18,
// Corresponding to a delta of timestamps of 4099680000 (or 130 years),
// much bigger than any realistic time difference between two syncs.
uint256 exponent = ((block.timestamp - lastSyncedTimestamp) * 1e18) / YEARLY_SECONDS;
// gas: Taking an imaginary worst-case scenario with max interest of 1000%
// over a period of 5 years.
// This won't overflow as long as openDebt < 3402823669209384912995114146594816
// which is 3.4 million billion *10**18 decimals.
unrealisedDebt = (realisedDebt * (LogExpMath.pow(base, exponent) - 1e18)) / 1e18;
}
return SafeCastLib.safeCastTo128(unrealisedDebt);
}
/**
* @notice Syncs interest payments to the liquidity providers and the treasury.
* @param assets The total amount of underlying assets to be paid out as interests.
* @dev The interest weight of each Tranche determines the relative share of yield (interest payments)
* that goes to its liquidity providers.
* @dev If the total interest weight is 0, all interests will go to the treasury.
*/
function _syncInterestsToLiquidityProviders(uint256 assets) internal {
uint256 remainingAssets = assets;
uint256 totalInterestWeight_ = totalInterestWeight;
if (totalInterestWeight_ > 0) {
uint256 realisedLiquidity;
uint256 trancheShare;
uint256 trancheLength = tranches.length;
for (uint256 i; i < trancheLength; ++i) {
realisedLiquidity = realisedLiquidityOf[tranches[i]];
// Don't pay interests to Tranches without liquidity.
// Interests will go to treasury instead.
if (realisedLiquidity == 0) continue;
trancheShare = assets.mulDivDown(interestWeightTranches[i], totalInterestWeight_);
unchecked {
realisedLiquidityOf[tranches[i]] = realisedLiquidity + trancheShare;
remainingAssets -= trancheShare;
}
}
}
unchecked {
totalRealisedLiquidity = SafeCastLib.safeCastTo128(totalRealisedLiquidity + assets);
// Add the remainingAssets to the treasury balance.
realisedLiquidityOf[treasury] += remainingAssets;
}
}
/* //////////////////////////////////////////////////////////////
INTEREST RATE LOGIC
////////////////////////////////////////////////////////////// */
/**
* @notice Sets the interest configuration parameters.
* @param baseRatePerYear_ The base interest rate per year.
* @param lowSlopePerYear_ The slope of the interest rate per year when the utilization rate is below the utilization threshold.
* @param highSlopePerYear_ The slope of the interest rate per year when the utilization rate exceeds the utilization threshold.
* @param utilisationThreshold_ The utilization threshold for determining the interest rate slope change.
* @dev We cannot use a struct to store all variables, since this would cause the contract size to exceed the maximum size.
*/
function setInterestParameters(
uint72 baseRatePerYear_,
uint72 lowSlopePerYear_,
uint72 highSlopePerYear_,
uint16 utilisationThreshold_
) external processInterests onlyOwner {
baseRatePerYear = baseRatePerYear_;
lowSlopePerYear = lowSlopePerYear_;
highSlopePerYear = highSlopePerYear_;
utilisationThreshold = utilisationThreshold_;
}
/**
* @notice Updates the interest rate.
* @dev Any address can call this, it will sync unrealised interests and update the interest rate.
*/
function updateInterestRate() external processInterests { }
/**
* @notice Updates the interest rate.
* @param totalDebt Total amount of debt.
* @param totalLiquidity_ Total amount of Liquidity (sum of borrowed out assets and assets still available in the Lending Pool).
*/
function _updateInterestRate(uint256 totalDebt, uint256 totalLiquidity_) internal {
uint256 utilisation; // 4 decimals precision
unchecked {
// This doesn't overflow since totalDebt is a uint128: uint128 * 10_000 < type(uint256).max.
if (totalLiquidity_ > 0) utilisation = totalDebt * ONE_4 / totalLiquidity_;
}
// Cap utilisation to 100%.
utilisation = utilisation <= ONE_4 ? utilisation : ONE_4;
emit PoolStateUpdated(totalDebt, totalLiquidity_, interestRate = _calculateInterestRate(utilisation));
}
/**
* @notice Calculates the interest rate.
* @param utilisation Utilisation rate, 4 decimal precision.
* @return interestRate_ The current interest rate, 18 decimal precision.
* @dev The interest rate is a function of the utilisation of the Lending Pool.
* We use two linear curves: one below the optimal utilisation with low slope and a steep one above.
*/
function _calculateInterestRate(uint256 utilisation) internal view returns (uint80 interestRate_) {
// While repays are paused, interest rate is set to 0.
if (repayPaused) return 0;
unchecked {
if (utilisation >= utilisationThreshold) {
// lsIR (1e22) = uT (1e4) * ls (1e18).
uint256 lowSlopeInterest = uint256(utilisationThreshold) * lowSlopePerYear;
// hsIR (1e22) = (u - uT) (1e4) * hs (e18).
uint256 highSlopeInterest = uint256(utilisation - utilisationThreshold) * highSlopePerYear;
// i (1e18) = (lsIR (e22) + hsIR (1e22)) / 1e4 + bs (1e18).
interestRate_ = uint80((lowSlopeInterest + highSlopeInterest) / ONE_4 + baseRatePerYear);
} else {
// i (1e18) = (u (1e4) * ls (1e18)) / 1e4 + br (1e18).
interestRate_ = uint80(utilisation * lowSlopePerYear / ONE_4 + baseRatePerYear);
}
}
}
/* //////////////////////////////////////////////////////////////
LIQUIDATION LOGIC
////////////////////////////////////////////////////////////// */
/**
* @notice Initiates the liquidation process for an Account.
* @param initiator The address of the liquidation initiator.
* @param minimumMargin_ The minimum margin of the Account.
* @return startDebt The initial debt of the liquidated Account.
* @dev This function is only callable by an Arcadia Account with debt.
* The liquidation process involves assessing the Account's debt and calculating liquidation incentives,
* which are considered as extra debt.
* The extra debt is then minted towards the Account to encourage the liquidation process and bring the Account to a healthy state.
* @dev Only Accounts with non-zero balances can have debt, and debtTokens are non-transferrable.
* @dev If the provided Account has a debt balance of 0, the function reverts with the error "IsNotAnAccountWithDebt."
*/
function startLiquidation(address initiator, uint256 minimumMargin_)
external
override
whenLiquidationNotPaused
processInterests
returns (uint256 startDebt)
{
// Only Accounts can have debt, and debtTokens are non-transferrable.
// Hence by checking that the balance of the msg.sender is not 0,
// we know that the sender is indeed an Account and has debt.
startDebt = maxWithdraw(msg.sender);
if (startDebt == 0) revert LendingPoolErrors.IsNotAnAccountWithDebt();
// Calculate liquidation incentives which have to be paid by the Account owner and are minted
// as extra debt to the Account.
(uint256 initiationReward, uint256 terminationReward, uint256 liquidationPenalty) =
_calculateRewards(startDebt, minimumMargin_);
// Mint the liquidation incentives as extra debt towards the Account.
_deposit(initiationReward + liquidationPenalty + terminationReward, msg.sender);
// Increase the realised liquidity for the initiator.
// The other incentives will only be added as realised liquidity for the respective actors
// after the auction is finished.
realisedLiquidityOf[initiator] += initiationReward;
totalRealisedLiquidity = SafeCastLib.safeCastTo128(totalRealisedLiquidity + initiationReward);
// If this is the sole ongoing auction, prevent any deposits and withdrawals in the most jr tranche
if (auctionsInProgress == 0 && tranches.length > 0) {
unchecked {
ITranche(tranches[tranches.length - 1]).setAuctionInProgress(true);
}
}
unchecked {
++auctionsInProgress;
}
// Emit event
emit AuctionStarted(msg.sender, address(this), uint128(startDebt));
}
/**
* @notice Ends the liquidation process for a specific Account and settles the liquidation incentives.
* @param account The address of the Account undergoing liquidation settlement.
* @param startDebt The initial debt amount of the liquidated Account.
* @param minimumMargin_ The minimum margin of the Account.
* @param terminator The address of the liquidation terminator.
* @dev In the happy flow, the auction proceeds are sufficient to pay off enough debt
* to bring the Account in a healthy position, and pay out all liquidation incentives to the
* relevant actors.
*/
function settleLiquidationHappyFlow(address account, uint256 startDebt, uint256 minimumMargin_, address terminator)
external
whenLiquidationNotPaused
onlyLiquidator
processInterests
{
_settleLiquidationHappyFlow(account, startDebt, minimumMargin_, terminator, 0);
}
/**
* @notice Ends the liquidation process for a specific Account and settles the liquidation incentives.
* @param account The address of the Account undergoing liquidation settlement.
* @param startDebt The initial debt amount of the liquidated Account.
* @param minimumMargin_ The minimum margin of the Account.
* @param terminator The address of the liquidation terminator.
* @param surplus The surplus amount obtained from the liquidation process.
* @dev In the happy flow, the auction proceeds are sufficient to pay off enough debt
* to bring the Account in a healthy position, and pay out all liquidation incentives to the
* relevant actors.
* @dev The following pending incentives are made claimable:
* - The "terminationReward", going towards the terminator of the auction.
* - The "liquidationFee", going towards LPs and the Treasury.
* - If there are still remaining assets after paying off all debt and incentives,
* the surplus goes towards the owner of the account.
*/
function _settleLiquidationHappyFlow(
address account,
uint256 startDebt,
uint256 minimumMargin_,
address terminator,
uint256 surplus
) internal {
(uint256 initiationReward, uint256 terminationReward, uint256 liquidationPenalty) =
_calculateRewards(startDebt, minimumMargin_);
// Pay out the "liquidationPenalty" to the most Junior Tranche and Treasury.
_syncLiquidationFee(liquidationPenalty);
totalRealisedLiquidity =
SafeCastLib.safeCastTo128(totalRealisedLiquidity + terminationReward + liquidationPenalty + surplus);
unchecked {
// Pay out any surplus to the current Account Owner.
if (surplus > 0) realisedLiquidityOf[IAccount(account).owner()] += surplus;
// Pay out the "terminationReward" to the "terminator".
realisedLiquidityOf[terminator] += terminationReward;
}
_endLiquidation();
emit AuctionFinished(
account, address(this), startDebt, initiationReward, terminationReward, liquidationPenalty, 0, surplus
);
}
/**
* @notice Ends the liquidation process for a specific Account and settles the liquidation incentives/bad debt.
* @param account The address of the Account undergoing liquidation settlement.
* @param startDebt The initial debt amount of the liquidated Account.
* @param minimumMargin_ The minimum margin of the Account.
* @param terminator The address of the auction terminator.
* @dev In the unhappy flow, the auction proceeds are not sufficient to pay out all liquidation incentives
* and maybe not even to pay off all debt.
* @dev The order in which incentives are not paid out/ bad debt is settled is fixed:
* - First, the "liquidationFee", going towards LPs and the Treasury is not paid out.
* - Next, the "terminationReward", going towards the terminator of the auction is not paid out.
* - Next, the underlying assets of LPs in the most junior Tranche are written off pro rata.
* - Next, the underlying assets of LPs in the second most junior Tranche are written off pro rata.
* - etc.
*/
function settleLiquidationUnhappyFlow(
address account,
uint256 startDebt,
uint256 minimumMargin_,
address terminator
) external whenLiquidationNotPaused onlyLiquidator processInterests {
(uint256 initiationReward, uint256 terminationReward, uint256 liquidationPenalty) =
_calculateRewards(startDebt, minimumMargin_);
// Any remaining debt that was not recovered during the auction must be written off.
// Depending on the size of the remaining debt, different stakeholders will be impacted.
uint256 debtShares = balanceOf[account];
uint256 openDebt = convertToAssets(debtShares);
uint256 badDebt;
if (openDebt > terminationReward + liquidationPenalty) {
// "openDebt" is bigger than pending liquidation incentives.
// No incentives will be paid out, and a default event is triggered.
unchecked {
badDebt = openDebt - terminationReward - liquidationPenalty;
}
totalRealisedLiquidity = uint128(totalRealisedLiquidity - badDebt);
_processDefault(badDebt);
} else {
uint256 remainder = liquidationPenalty + terminationReward - openDebt;
if (openDebt >= liquidationPenalty) {
// "openDebt" is bigger than the "liquidationPenalty" but smaller than the total pending liquidation incentives.
// Don't pay out the "liquidationPenalty" to Lps, partially pay out the "terminator".
realisedLiquidityOf[terminator] += remainder;
} else {
// "openDebt" is smaller than the "liquidationPenalty".
// Fully pay out the "terminator" and partially pay out the "liquidationPenalty".
realisedLiquidityOf[terminator] += terminationReward;
_syncLiquidationFee(remainder - terminationReward);
}
totalRealisedLiquidity = SafeCastLib.safeCastTo128(totalRealisedLiquidity + remainder);
}
// Remove the remaining debt from the Account now that it is written off from the liquidation incentives/Liquidity Providers.
_burn(account, debtShares);
realisedDebt -= openDebt;
emit Withdraw(msg.sender, account, account, openDebt, debtShares);
_endLiquidation();
emit AuctionFinished(
account, address(this), startDebt, initiationReward, terminationReward, liquidationPenalty, badDebt, 0
);
}
/**
* @notice Ends the liquidation.
* @dev Unlocks the most junior Tranche if there are no other liquidations ongoing.
*/
function _endLiquidation() internal {
// Decrement the number of auctions in progress.
unchecked {
--auctionsInProgress;
}
// Hook to the most junior Tranche.
if (auctionsInProgress == 0 && tranches.length > 0) {
unchecked {
ITranche(tranches[tranches.length - 1]).setAuctionInProgress(false);
}
}
}
/**
* @notice Handles the accounting in case of bad debt (Account became undercollateralised).
* @param badDebt The total amount of underlying assets that need to be written off as bad debt.
* @dev The order of the Tranches is important, the most senior Tranche is at index 0, the most junior at the last index.
* @dev The most junior tranche will lose its underlying assets first. If all liquidity of a certain Tranche is written off,
* the complete tranche is locked and removed. If there is still remaining bad debt, the next Tranche starts losing capital.
* @dev If all Tranches are written off and there is still remaining badDebt, the accounting of the pool no longer holds
* (sum of all realisedLiquidityOf() balances is bigger then totalRealisedLiquidity).
* In this case no new Tranches should be added to restart the LendingPool and any remaining funds should be withdrawn.
*/
function _processDefault(uint256 badDebt) internal {
address tranche;
uint256 maxBurnable;
uint256 length = tranches.length;
for (uint256 i = length; i > 0;) {
unchecked {
--i;
}
tranche = tranches[i];
maxBurnable = realisedLiquidityOf[tranche];
if (badDebt < maxBurnable) {
// Deduct badDebt from the balance of the most junior Tranche.
unchecked {
realisedLiquidityOf[tranche] -= badDebt;
}
break;
} else {
// Unhappy flow, should never occur in practice!
// badDebt is bigger than the balance of most junior Tranche -> tranche is completely wiped out
// and temporarily locked (no new deposits or withdraws possible).
// DAO or insurance might refund (Part of) the losses, and add Tranche back.
realisedLiquidityOf[tranche] = 0;
_popTranche(i, tranche);
unchecked {
badDebt -= maxBurnable;
}
ITranche(tranche).lock();
// Hook to the new most junior Tranche to inform that auctions are ongoing.
if (i != 0) ITranche(tranches[i - 1]).setAuctionInProgress(true);
}
}
}
/**
* @notice Syncs liquidation penalties to the most Junior Tranche and the treasury.
* @param assets The total amount of underlying assets to be paid out as liquidation fee.
* @dev The liquidationWeightTranche and liquidationWeightTreasury determines the relative share of yield (liquidation penalties)
* that goes to the most Junior Tranche and the treasury.
* @dev If the total liquidation weight is 0, the liquidation fee is added to the treasury.
*/
function _syncLiquidationFee(uint256 assets) internal {
// Cache storage variables.
uint256 length = tranches.length;
uint256 weightTranche = liquidationWeightTranche;
uint256 totalWeight;
unchecked {
totalWeight = weightTranche + liquidationWeightTreasury;
}
// Sync fee to the most Junior Tranche (last index).
if (totalWeight > 0 && length > 0) {
uint256 realisedLiquidity = realisedLiquidityOf[tranches[length - 1]];
// Don't pay fees to a Tranche without liquidity.
// Interests will go to treasury instead.
if (realisedLiquidity > 0) {
uint256 trancheFee = assets.mulDivDown(weightTranche, totalWeight);
unchecked {
realisedLiquidityOf[tranches[length - 1]] = realisedLiquidity + trancheFee;
assets -= trancheFee;
}
}
}
// Add the remaining fee to the treasury balance.
unchecked {
realisedLiquidityOf[treasury] += assets;
}
}
/**
* @notice Calculates the rewards and penalties for the liquidation process based on the given debt amount.
* @param debt The debt amount of the Account at the time of liquidation initiation.
* @param minimumMargin_ The minimum margin of the Account.
* @return initiationReward The reward for the liquidation initiator, capped by the maximum initiator reward.
* @return terminationReward The reward for closing the liquidation process, capped by the maximum termination reward.
* @return liquidationPenalty The penalty paid by the Account owner towards the liquidity providers and the protocol treasury.
* @dev The rewards for the initiator and terminator should at least cover the gas costs.
* -> minimumMargin should be set big enough such that "minimumMargin * minRewardWeight" can cover any possible gas cost to initiate/terminate the liquidation.
* @dev Since the initiation/termination costs do not increase with position size, the initiator and terminator rewards can be capped to a maximum value.
*/
function _calculateRewards(uint256 debt, uint256 minimumMargin_)
internal
view
returns (uint256 initiationReward, uint256 terminationReward, uint256 liquidationPenalty)
{
uint256 maxReward_ = maxReward;
// The minimum reward, for both the initiation- and terminationReward, is defined as a fixed percentage of the minimumMargin.
uint256 minReward = minimumMargin_.mulDivUp(minRewardWeight, ONE_4);
// Initiation reward must be between minReward and maxReward.
initiationReward = debt.mulDivDown(initiationWeight, ONE_4);
initiationReward = initiationReward > minReward ? initiationReward : minReward;
initiationReward = initiationReward > maxReward_ ? maxReward_ : initiationReward;
// Termination reward must be between minReward and maxReward.
terminationReward = debt.mulDivDown(terminationWeight, ONE_4);
terminationReward = terminationReward > minReward ? terminationReward : minReward;
terminationReward = terminationReward > maxReward_ ? maxReward_ : terminationReward;
liquidationPenalty = debt.mulDivUp(penaltyWeight, ONE_4);
}
/*///////////////////////////////////////////////////////////////
MANAGE AUCTION SETTINGS
///////////////////////////////////////////////////////////////*/
/**
* @notice Sets the liquidation parameters.
* @param initiationWeight_ Reward paid to the Liquidation Initiator.
* @param penaltyWeight_ Penalty paid by the Account owner to the Creditor.
* @param terminationWeight_ Reward paid to the Liquidation closer.
* @param minRewardWeight_ The minimum reward that is paid to the initiator/terminator of a liquidation.
* @param maxReward_ The maximum reward that is paid to the initiator/terminator of a liquidation.
* @dev Each weight has 4 decimals precision (50 equals 0,005 or 0,5%).
* @dev Each weight sets the % of the debt that is paid as reward to the initiator and terminator of a liquidation.
* This reward is capped in absolute value by the maxReward respectively maxReward.
* @dev We cannot use a struct to store all variables, since this would cause the contract size to exceed the maximum size.
*/
function setLiquidationParameters(
uint16 initiationWeight_,
uint16 penaltyWeight_,
uint16 terminationWeight_,
uint16 minRewardWeight_,
uint80 maxReward_
) external onlyOwner {
// When auctions are ongoing, it is not allowed to modify the auction parameters,
// as that would corrupt the rewards and penalties calculated by _calculateRewards().
if (auctionsInProgress != 0) revert LendingPoolErrors.AuctionOngoing();
// Total penalties/rewards, paid by the Account cannot exceed MAX_TOTAL_PENALTY.
if (uint256(initiationWeight_) + penaltyWeight_ + terminationWeight_ > MAX_TOTAL_PENALTY) {
revert LendingPoolErrors.LiquidationWeightsTooHigh();
}
// Sum of the initiationReward and terminationReward cannot exceed minimumMargin of the Account.
// -> minRewardWeight is capped to 50%.
if (minRewardWeight_ > 5000) revert LendingPoolErrors.LiquidationWeightsTooHigh();
// Store new parameters.
initiationWeight = initiationWeight_;
penaltyWeight = penaltyWeight_;
terminationWeight = terminationWeight_;
minRewardWeight = minRewardWeight_;
maxReward = maxReward_;
}
/**
* @notice Sets the minimum amount of collateral that must be held in an Account before a position can be opened.
* @param minimumMargin_ The new minimumMargin.
* @dev The minimum margin should be a conservative upper estimate of the maximal gas cost to liquidate a position (fixed cost, independent of openDebt).
* The minimumMargin prevents dusting attacks, and ensures that upon liquidations positions are big enough to cover
* network transaction costs while remaining attractive to liquidate.
*/
function setMinimumMargin(uint96 minimumMargin_) external onlyOwner {
minimumMargin = minimumMargin_;
}
/* //////////////////////////////////////////////////////////////
ACCOUNT LOGIC
////////////////////////////////////////////////////////////// */
/**
* @notice Sets a new Risk Manager.
* @param riskManager_ The address of the new Risk Manager.
*/
function setRiskManager(address riskManager_) external onlyOwner {
_setRiskManager(riskManager_);
}
/**
* @notice Enables or disables a certain Account version to be used as margin account.
* @param accountVersion the Account version to be enabled/disabled.
* @param valid The validity of the respective accountVersion.
*/
function setAccountVersion(uint256 accountVersion, bool valid) external onlyOwner {
_setAccountVersion(accountVersion, valid);
}
/**
* @inheritdoc Creditor
*/
function openMarginAccount(uint256 accountVersion)
external
view
override
returns (bool success, address numeraire, address liquidator_, uint256 minimumMargin_)
{
if (isValidVersion[accountVersion]) {
success = true;
numeraire = address(asset);
liquidator_ = LIQUIDATOR;
minimumMargin_ = minimumMargin;
}
}
/**
* @inheritdoc Creditor
*/
function closeMarginAccount(address account) external view override {
if (maxWithdraw(account) != 0) revert LendingPoolErrors.OpenPositionNonZero();
}
/**
* @inheritdoc Creditor
*/
function getOpenPosition(address account) external view override returns (uint256 openPosition) {
openPosition = maxWithdraw(account);
}
/* //////////////////////////////////////////////////////////////
HELPER FUNCTIONS
////////////////////////////////////////////////////////////// */
/**
* @notice Returns the configuration of the interest rate slopes.
* @return baseRatePerYear The base interest rate per year.
* @return lowSlopePerYear The slope of the interest rate per year when the utilization rate is below the utilization threshold.
* @return highSlopePerYear The slope of the interest rate per year when the utilization rate exceeds the utilization threshold.
* @return utilisationThreshold The utilization threshold for determining the interest rate slope change.
*/
function getInterestRateConfig() external view returns (uint72, uint72, uint72, uint16) {
return (baseRatePerYear, lowSlopePerYear, highSlopePerYear, utilisationThreshold);
}
/**
* @notice Returns the liquidation parameters.
* @return initiationWeight Reward paid to the Liquidation Initiator.
* @return penaltyWeight Penalty paid by the Account owner to the Creditor.
* @return terminationWeight Reward paid to the Liquidation closer.
* @return minRewardWeight The minimum reward that is paid to the initiator/terminator of a liquidation.
* @return maxReward The maximum reward that is paid to the initiator/terminator of a liquidation.
*/
function getLiquidationParameters() external view returns (uint16, uint16, uint16, uint16, uint80) {
return (initiationWeight, penaltyWeight, terminationWeight, minRewardWeight, maxReward);
}
}
/**
* Created by Pragma Labs
* SPDX-License-Identifier: BUSL-1.1
*/
pragma solidity 0.8.22;
import { BaseGuardian, GuardianErrors } from "../../lib/accounts-v2/src/guardians/BaseGuardian.sol";
/**
* @title LendingPool Guardian.
* @author Pragma Labs
* @notice Logic inherited by the LendingPool that allows:
* - An authorized guardian to trigger an emergency stop.
* - The protocol owner to unpause functionalities one-by-one.
* - Anyone to unpause all functionalities after a fixed cool-down period.
*/
abstract contract LendingPoolGuardian is BaseGuardian {
/* //////////////////////////////////////////////////////////////
STORAGE
////////////////////////////////////////////////////////////// */
// Flag indicating if the repay() function is paused.
bool public repayPaused;
// Flag indicating if the withdraw() function is paused.
bool public withdrawPaused;
// Flag indicating if the borrow() function is paused.
bool public borrowPaused;
// Flag indicating if the deposit() function is paused.
bool public depositPaused;
// Flag indicating if the liquidation() function is paused.
bool public liquidationPaused;
/* //////////////////////////////////////////////////////////////
EVENTS
////////////////////////////////////////////////////////////// */
event PauseFlagsUpdated(
bool repayPauseFlagsUpdated,
bool withdrawPauseFlagsUpdated,
bool borrowPauseFlagsUpdated,
bool depositPauseFlagsUpdated,
bool liquidationPauseFlagsUpdated
);
/* //////////////////////////////////////////////////////////////
MODIFIERS
////////////////////////////////////////////////////////////// */
/**
* @dev Throws if the repay functionality is paused.
*/
modifier whenRepayNotPaused() {
if (repayPaused) revert GuardianErrors.FunctionIsPaused();
_;
}
/**
* @dev Throws if the withdraw functionality is paused.
*/
modifier whenWithdrawNotPaused() {
if (withdrawPaused) revert GuardianErrors.FunctionIsPaused();
_;
}
/**
* @dev Throws if the borrow functionality is paused.
*/
modifier whenBorrowNotPaused() {
if (borrowPaused) revert GuardianErrors.FunctionIsPaused();
_;
}
/**
* @dev Throws if the deposit functionality is paused.
*/
modifier whenDepositNotPaused() {
if (depositPaused) revert GuardianErrors.FunctionIsPaused();
_;
}
/**
* @dev Throws if the liquidation functionality is paused.
*/
modifier whenLiquidationNotPaused() {
if (liquidationPaused) revert GuardianErrors.FunctionIsPaused();
_;
}
/* //////////////////////////////////////////////////////////////
PAUSING LOGIC
////////////////////////////////////////////////////////////// */
/**
* @inheritdoc BaseGuardian
* @dev This function will pause the functionality to:
* - Repay debt.
* - Withdraw liquidity.
* - Borrow.
* - Deposit liquidity.
* - Liquidate positions.
*/
function pause() external override onlyGuardian afterCoolDownOf(32 days) {
pauseTimestamp = uint96(block.timestamp);
emit PauseFlagsUpdated(
repayPaused = true,
withdrawPaused = true,
borrowPaused = true,
depositPaused = true,
liquidationPaused = true
);
}
/**
* @notice This function is used to unpause one or more flags.
* @param repayPaused_ False when repay functionality should be unPaused.
* @param withdrawPaused_ False when withdraw functionality should be unPaused.
* @param borrowPaused_ False when borrow functionality should be unPaused.
* @param depositPaused_ False when deposit functionality should be unPaused.
* @param liquidationPaused_ False when liquidation functionality should be unPaused.
* @dev This function can unPause repay, withdraw, borrow, and deposit individually.
* @dev Can only update flags from paused (true) to unPaused (false), cannot be used the other way around
* (to set unPaused flags to paused).
*/
function unpause(
bool repayPaused_,
bool withdrawPaused_,
bool borrowPaused_,
bool depositPaused_,
bool liquidationPaused_
) external onlyOwner {
emit PauseFlagsUpdated(
repayPaused = repayPaused && repayPaused_,
withdrawPaused = withdrawPaused && withdrawPaused_,
borrowPaused = borrowPaused && borrowPaused_,
depositPaused = depositPaused && depositPaused_,
liquidationPaused = liquidationPaused && liquidationPaused_
);
}
/**
* @inheritdoc BaseGuardian
* @dev This function will unpause the functionality to:
* - Repay debt.
* - Withdraw liquidity.
* - Liquidate positions.
*/
function unpause() external override afterCoolDownOf(30 days) {
emit PauseFlagsUpdated(
repayPaused = false, withdrawPaused = false, borrowPaused, depositPaused, liquidationPaused = false
);
}
}
// SPDX-License-Identifier: MIT
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the “Software”), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
// Software.
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
pragma solidity 0.8.22;
import "./BalancerErrors.sol";
/* solhint-disable */
/**
* @dev Exponentiation and logarithm functions for 18 decimal fixed point numbers (both base and exponent/argument).
*
* Exponentiation and logarithm with arbitrary bases (x^y and log_x(y)) are implemented by conversion to natural
* exponentiation and logarithm (where the base is Euler's number).
*
* @author Fernando Martinelli - @fernandomartinelli
* @author Sergio Yuhjtman - @sergioyuhjtman
* @author Daniel Fernandez - @dmf7z
*/
library LogExpMath {
// All fixed point multiplications and divisions are inlined. This means we need to divide by ONE when multiplying
// two numbers, and multiply by ONE when dividing them.
// All arguments and return values are 18 decimal fixed point numbers.
int256 constant ONE_18 = 1e18;
// Internally, intermediate values are computed with higher precision as 20 decimal fixed point numbers, and in the
// case of ln36, 36 decimals.
int256 constant ONE_20 = 1e20;
int256 constant ONE_36 = 1e36;
// The domain of natural exponentiation is bound by the word size and number of decimals used.
//
// Because internally the result will be stored using 20 decimals, the largest possible result is
// (2^255 - 1) / 10^20, which makes the largest exponent ln((2^255 - 1) / 10^20) = 130.700829182905140221.
// The smallest possible result is 10^(-18), which makes largest negative argument
// ln(10^(-18)) = -41.446531673892822312.
// We use 130.0 and -41.0 to have some safety margin.
int256 constant MAX_NATURAL_EXPONENT = 130e18;
int256 constant MIN_NATURAL_EXPONENT = -41e18;
// Bounds for ln_36's argument. Both ln(0.9) and ln(1.1) can be represented with 36 decimal places in a fixed point
// 256 bit integer.
int256 constant LN_36_LOWER_BOUND = ONE_18 - 1e17;
int256 constant LN_36_UPPER_BOUND = ONE_18 + 1e17;
uint256 constant MILD_EXPONENT_BOUND = 2 ** 254 / uint256(ONE_20);
// 18 decimal constants
int256 constant x0 = 128_000_000_000_000_000_000; // 2ˆ7
int256 constant a0 = 38_877_084_059_945_950_922_200_000_000_000_000_000_000_000_000_000_000_000; // eˆ(x0) (no decimals)
int256 constant x1 = 64_000_000_000_000_000_000; // 2ˆ6
int256 constant a1 = 6_235_149_080_811_616_882_910_000_000; // eˆ(x1) (no decimals)
// 20 decimal constants
int256 constant x2 = 3_200_000_000_000_000_000_000; // 2ˆ5
int256 constant a2 = 7_896_296_018_268_069_516_100_000_000_000_000; // eˆ(x2)
int256 constant x3 = 1_600_000_000_000_000_000_000; // 2ˆ4
int256 constant a3 = 888_611_052_050_787_263_676_000_000; // eˆ(x3)
int256 constant x4 = 800_000_000_000_000_000_000; // 2ˆ3
int256 constant a4 = 298_095_798_704_172_827_474_000; // eˆ(x4)
int256 constant x5 = 400_000_000_000_000_000_000; // 2ˆ2
int256 constant a5 = 5_459_815_003_314_423_907_810; // eˆ(x5)
int256 constant x6 = 200_000_000_000_000_000_000; // 2ˆ1
int256 constant a6 = 738_905_609_893_065_022_723; // eˆ(x6)
int256 constant x7 = 100_000_000_000_000_000_000; // 2ˆ0
int256 constant a7 = 271_828_182_845_904_523_536; // eˆ(x7)
int256 constant x8 = 50_000_000_000_000_000_000; // 2ˆ-1
int256 constant a8 = 164_872_127_070_012_814_685; // eˆ(x8)
int256 constant x9 = 25_000_000_000_000_000_000; // 2ˆ-2
int256 constant a9 = 128_402_541_668_774_148_407; // eˆ(x9)
int256 constant x10 = 12_500_000_000_000_000_000; // 2ˆ-3
int256 constant a10 = 113_314_845_306_682_631_683; // eˆ(x10)
int256 constant x11 = 6_250_000_000_000_000_000; // 2ˆ-4
int256 constant a11 = 106_449_445_891_785_942_956; // eˆ(x11)
/**
* @dev Exponentiation (x^y) with unsigned 18 decimal fixed point base and exponent.
*
* Reverts if ln(x) * y is smaller than `MIN_NATURAL_EXPONENT`, or larger than `MAX_NATURAL_EXPONENT`.
*/
function pow(uint256 x, uint256 y) internal pure returns (uint256) {
if (y == 0) {
// We solve the 0^0 indetermination by making it equal one.
return uint256(ONE_18);
}
if (x == 0) {
return 0;
}
// Instead of computing x^y directly, we instead rely on the properties of logarithms and exponentiation to
// arrive at that result. In particular, exp(ln(x)) = x, and ln(x^y) = y * ln(x). This means
// x^y = exp(y * ln(x)).
// The ln function takes a signed value, so we need to make sure x fits in the signed 256 bit range.
_require(x < 2 ** 255, Errors.X_OUT_OF_BOUNDS);
int256 x_int256 = int256(x);
// We will compute y * ln(x) in a single step. Depending on the value of x, we can either use ln or ln_36. In
// both cases, we leave the division by ONE_18 (due to fixed point multiplication) to the end.
// This prevents y * ln(x) from overflowing, and at the same time guarantees y fits in the signed 256 bit range.
_require(y < MILD_EXPONENT_BOUND, Errors.Y_OUT_OF_BOUNDS);
int256 y_int256 = int256(y);
int256 logx_times_y;
if (LN_36_LOWER_BOUND < x_int256 && x_int256 < LN_36_UPPER_BOUND) {
int256 ln_36_x = _ln_36(x_int256);
// ln_36_x has 36 decimal places, so multiplying by y_int256 isn't as straightforward, since we can't just
// bring y_int256 to 36 decimal places, as it might overflow. Instead, we perform two 18 decimal
// multiplications and add the results: one with the first 18 decimals of ln_36_x, and one with the
// (downscaled) last 18 decimals.
logx_times_y = ((ln_36_x / ONE_18) * y_int256 + ((ln_36_x % ONE_18) * y_int256) / ONE_18);
} else {
logx_times_y = _ln(x_int256) * y_int256;
}
logx_times_y /= ONE_18;
// Finally, we compute exp(y * ln(x)) to arrive at x^y
_require(
MIN_NATURAL_EXPONENT <= logx_times_y && logx_times_y <= MAX_NATURAL_EXPONENT, Errors.PRODUCT_OUT_OF_BOUNDS
);
return uint256(exp(logx_times_y));
}
/**
* @dev Natural exponentiation (e^x) with signed 18 decimal fixed point exponent.
*
* Reverts if `x` is smaller than MIN_NATURAL_EXPONENT, or larger than `MAX_NATURAL_EXPONENT`.
*/
function exp(int256 x) internal pure returns (int256) {
_require(x >= MIN_NATURAL_EXPONENT && x <= MAX_NATURAL_EXPONENT, Errors.INVALID_EXPONENT);
if (x < 0) {
// We only handle positive exponents: e^(-x) is computed as 1 / e^x. We can safely make x positive since it
// fits in the signed 256 bit range (as it is larger than MIN_NATURAL_EXPONENT).
// Fixed point division requires multiplying by ONE_18.
return ((ONE_18 * ONE_18) / exp(-x));
}
// First, we use the fact that e^(x+y) = e^x * e^y to decompose x into a sum of powers of two, which we call x_n,
// where x_n == 2^(7 - n), and e^x_n = a_n has been precomputed. We choose the first x_n, x0, to equal 2^7
// because all larger powers are larger than MAX_NATURAL_EXPONENT, and therefore not present in the
// decomposition.
// At the end of this process we will have the product of all e^x_n = a_n that apply, and the remainder of this
// decomposition, which will be lower than the smallest x_n.
// exp(x) = k_0 * a_0 * k_1 * a_1 * ... + k_n * a_n * exp(remainder), where each k_n equals either 0 or 1.
// We mutate x by subtracting x_n, making it the remainder of the decomposition.
// The first two a_n (e^(2^7) and e^(2^6)) are too large if stored as 18 decimal numbers, and could cause
// intermediate overflows. Instead we store them as plain integers, with 0 decimals.
// Additionally, x0 + x1 is larger than MAX_NATURAL_EXPONENT, which means they will not both be present in the
// decomposition.
// For each x_n, we test if that term is present in the decomposition (if x is larger than it), and if so deduct
// it and compute the accumulated product.
int256 firstAN;
if (x >= x0) {
x -= x0;
firstAN = a0;
} else if (x >= x1) {
x -= x1;
firstAN = a1;
} else {
firstAN = 1; // One with no decimal places
}
// We now transform x into a 20 decimal fixed point number, to have enhanced precision when computing the
// smaller terms.
x *= 100;
// `product` is the accumulated product of all a_n (except a0 and a1), which starts at 20 decimal fixed point
// one. Recall that fixed point multiplication requires dividing by ONE_20.
int256 product = ONE_20;
if (x >= x2) {
x -= x2;
product = (product * a2) / ONE_20;
}
if (x >= x3) {
x -= x3;
product = (product * a3) / ONE_20;
}
if (x >= x4) {
x -= x4;
product = (product * a4) / ONE_20;
}
if (x >= x5) {
x -= x5;
product = (product * a5) / ONE_20;
}
if (x >= x6) {
x -= x6;
product = (product * a6) / ONE_20;
}
if (x >= x7) {
x -= x7;
product = (product * a7) / ONE_20;
}
if (x >= x8) {
x -= x8;
product = (product * a8) / ONE_20;
}
if (x >= x9) {
x -= x9;
product = (product * a9) / ONE_20;
}
// x10 and x11 are unnecessary here since we have high enough precision already.
// Now we need to compute e^x, where x is small (in particular, it is smaller than x9). We use the Taylor series
// expansion for e^x: 1 + x + (x^2 / 2!) + (x^3 / 3!) + ... + (x^n / n!).
int256 seriesSum = ONE_20; // The initial one in the sum, with 20 decimal places.
int256 term; // Each term in the sum, where the nth term is (x^n / n!).
// The first term is simply x.
term = x;
seriesSum += term;
// Each term (x^n / n!) equals the previous one times x, divided by n. Since x is a fixed point number,
// multiplying by it requires dividing by ONE_20, but dividing by the non-fixed point n values does not.
term = ((term * x) / ONE_20) / 2;
seriesSum += term;
term = ((term * x) / ONE_20) / 3;
seriesSum += term;
term = ((term * x) / ONE_20) / 4;
seriesSum += term;
term = ((term * x) / ONE_20) / 5;
seriesSum += term;
term = ((term * x) / ONE_20) / 6;
seriesSum += term;
term = ((term * x) / ONE_20) / 7;
seriesSum += term;
term = ((term * x) / ONE_20) / 8;
seriesSum += term;
term = ((term * x) / ONE_20) / 9;
seriesSum += term;
term = ((term * x) / ONE_20) / 10;
seriesSum += term;
term = ((term * x) / ONE_20) / 11;
seriesSum += term;
term = ((term * x) / ONE_20) / 12;
seriesSum += term;
// 12 Taylor terms are sufficient for 18 decimal precision.
// We now have the first a_n (with no decimals), and the product of all other a_n present, and the Taylor
// approximation of the exponentiation of the remainder (both with 20 decimals). All that remains is to multiply
// all three (one 20 decimal fixed point multiplication, dividing by ONE_20, and one integer multiplication),
// and then drop two digits to return an 18 decimal value.
return (((product * seriesSum) / ONE_20) * firstAN) / 100;
}
/**
* @dev Internal natural logarithm (ln(a)) with signed 18 decimal fixed point argument.
*/
function _ln(int256 a) private pure returns (int256) {
if (a < ONE_18) {
// Since ln(a^k) = k * ln(a), we can compute ln(a) as ln(a) = ln((1/a)^(-1)) = - ln((1/a)). If a is less
// than one, 1/a will be greater than one, and this if statement will not be entered in the recursive call.
// Fixed point division requires multiplying by ONE_18.
return (-_ln((ONE_18 * ONE_18) / a));
}
// First, we use the fact that ln^(a * b) = ln(a) + ln(b) to decompose ln(a) into a sum of powers of two, which
// we call x_n, where x_n == 2^(7 - n), which are the natural logarithm of precomputed quantities a_n (that is,
// ln(a_n) = x_n). We choose the first x_n, x0, to equal 2^7 because the exponential of all larger powers cannot
// be represented as 18 fixed point decimal numbers in 256 bits, and are therefore larger than a.
// At the end of this process we will have the sum of all x_n = ln(a_n) that apply, and the remainder of this
// decomposition, which will be lower than the smallest a_n.
// ln(a) = k_0 * x_0 + k_1 * x_1 + ... + k_n * x_n + ln(remainder), where each k_n equals either 0 or 1.
// We mutate a by subtracting a_n, making it the remainder of the decomposition.
// For reasons related to how `exp` works, the first two a_n (e^(2^7) and e^(2^6)) are not stored as fixed point
// numbers with 18 decimals, but instead as plain integers with 0 decimals, so we need to multiply them by
// ONE_18 to convert them to fixed point.
// For each a_n, we test if that term is present in the decomposition (if a is larger than it), and if so divide
// by it and compute the accumulated sum.
int256 sum = 0;
if (a >= a0 * ONE_18) {
a /= a0; // Integer, not fixed point division
sum += x0;
}
if (a >= a1 * ONE_18) {
a /= a1; // Integer, not fixed point division
sum += x1;
}
// All other a_n and x_n are stored as 20 digit fixed point numbers, so we convert the sum and a to this format.
sum *= 100;
a *= 100;
// Because further a_n are 20 digit fixed point numbers, we multiply by ONE_20 when dividing by them.
if (a >= a2) {
a = (a * ONE_20) / a2;
sum += x2;
}
if (a >= a3) {
a = (a * ONE_20) / a3;
sum += x3;
}
if (a >= a4) {
a = (a * ONE_20) / a4;
sum += x4;
}
if (a >= a5) {
a = (a * ONE_20) / a5;
sum += x5;
}
if (a >= a6) {
a = (a * ONE_20) / a6;
sum += x6;
}
if (a >= a7) {
a = (a * ONE_20) / a7;
sum += x7;
}
if (a >= a8) {
a = (a * ONE_20) / a8;
sum += x8;
}
if (a >= a9) {
a = (a * ONE_20) / a9;
sum += x9;
}
if (a >= a10) {
a = (a * ONE_20) / a10;
sum += x10;
}
if (a >= a11) {
a = (a * ONE_20) / a11;
sum += x11;
}
// a is now a small number (smaller than a_11, which roughly equals 1.06). This means we can use a Taylor series
// that converges rapidly for values of `a` close to one - the same one used in ln_36.
// Let z = (a - 1) / (a + 1).
// ln(a) = 2 * (z + z^3 / 3 + z^5 / 5 + z^7 / 7 + ... + z^(2 * n + 1) / (2 * n + 1))
// Recall that 20 digit fixed point division requires multiplying by ONE_20, and multiplication requires
// division by ONE_20.
int256 z = ((a - ONE_20) * ONE_20) / (a + ONE_20);
int256 z_squared = (z * z) / ONE_20;
// num is the numerator of the series: the z^(2 * n + 1) term
int256 num = z;
// seriesSum holds the accumulated sum of each term in the series, starting with the initial z
int256 seriesSum = num;
// In each step, the numerator is multiplied by z^2
num = (num * z_squared) / ONE_20;
seriesSum += num / 3;
num = (num * z_squared) / ONE_20;
seriesSum += num / 5;
num = (num * z_squared) / ONE_20;
seriesSum += num / 7;
num = (num * z_squared) / ONE_20;
seriesSum += num / 9;
num = (num * z_squared) / ONE_20;
seriesSum += num / 11;
// 6 Taylor terms are sufficient for 36 decimal precision.
// Finally, we multiply by 2 (non fixed point) to compute ln(remainder)
seriesSum *= 2;
// We now have the sum of all x_n present, and the Taylor approximation of the logarithm of the remainder (both
// with 20 decimals). All that remains is to sum these two, and then drop two digits to return a 18 decimal
// value.
return (sum + seriesSum) / 100;
}
/**
* @dev Intrnal high precision (36 decimal places) natural logarithm (ln(x)) with signed 18 decimal fixed point argument,
* for x close to one.
*
* Should only be used if x is between LN_36_LOWER_BOUND and LN_36_UPPER_BOUND.
*/
function _ln_36(int256 x) private pure returns (int256) {
// Since ln(1) = 0, a value of x close to one will yield a very small result, which makes using 36 digits
// worthwhile.
// First, we transform x to a 36 digit fixed point value.
x *= ONE_18;
// We will use the following Taylor expansion, which converges very rapidly. Let z = (x - 1) / (x + 1).
// ln(x) = 2 * (z + z^3 / 3 + z^5 / 5 + z^7 / 7 + ... + z^(2 * n + 1) / (2 * n + 1))
// Recall that 36 digit fixed point division requires multiplying by ONE_36, and multiplication requires
// division by ONE_36.
int256 z = ((x - ONE_36) * ONE_36) / (x + ONE_36);
int256 z_squared = (z * z) / ONE_36;
// num is the numerator of the series: the z^(2 * n + 1) term
int256 num = z;
// seriesSum holds the accumulated sum of each term in the series, starting with the initial z
int256 seriesSum = num;
// In each step, the numerator is multiplied by z^2
num = (num * z_squared) / ONE_36;
seriesSum += num / 3;
num = (num * z_squared) / ONE_36;
seriesSum += num / 5;
num = (num * z_squared) / ONE_36;
seriesSum += num / 7;
num = (num * z_squared) / ONE_36;
seriesSum += num / 9;
num = (num * z_squared) / ONE_36;
seriesSum += num / 11;
num = (num * z_squared) / ONE_36;
seriesSum += num / 13;
num = (num * z_squared) / ONE_36;
seriesSum += num / 15;
// 8 Taylor terms are sufficient for 36 decimal precision.
// All that remains is multiplying by 2 (non fixed point).
return seriesSum * 2;
}
}
// 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 Safe unsigned integer casting library that reverts on overflow.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeCastLib.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/SafeCast.sol)
library SafeCastLib {
function safeCastTo128(uint256 x) internal pure returns (uint128 y) {
require(x < 1 << 128);
y = uint128(x);
}
}
// 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");
}
}
{
"compilationTarget": {
"src/LendingPool.sol": "LendingPool"
},
"evmVersion": "shanghai",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": [
":@openzeppelin/=lib/accounts-v2/lib/openzeppelin-contracts/",
":@uniswap/v3-core/contracts/=lib/accounts-v2/lib/v3-core/contracts/",
":accounts-v2/=lib/accounts-v2/",
":ds-test/=lib/forge-std/lib/ds-test/src/",
":forge-std/=lib/forge-std/src/",
":openzeppelin-contracts/=lib/accounts-v2/lib/openzeppelin-contracts/contracts/",
":solmate/=lib/solmate/src/",
":v3-core/=lib/accounts-v2/lib/v3-core/",
":v3-periphery/=lib/accounts-v2/lib/v3-periphery/contracts/"
]
}
[{"inputs":[{"internalType":"address","name":"riskManager_","type":"address"},{"internalType":"contract ERC20","name":"asset_","type":"address"},{"internalType":"address","name":"treasury_","type":"address"},{"internalType":"address","name":"accountFactory","type":"address"},{"internalType":"address","name":"liquidator","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AmountExceedsBalance","type":"error"},{"inputs":[],"name":"AuctionOngoing","type":"error"},{"inputs":[],"name":"CoolDownPeriodNotPassed","type":"error"},{"inputs":[],"name":"FunctionIsPaused","type":"error"},{"inputs":[],"name":"FunctionNotImplemented","type":"error"},{"inputs":[],"name":"InvalidVersion","type":"error"},{"inputs":[],"name":"IsNotAnAccount","type":"error"},{"inputs":[],"name":"IsNotAnAccountWithDebt","type":"error"},{"inputs":[],"name":"LiquidationWeightsTooHigh","type":"error"},{"inputs":[],"name":"NonExistingTranche","type":"error"},{"inputs":[],"name":"OnlyGuardian","type":"error"},{"inputs":[],"name":"OpenPositionNonZero","type":"error"},{"inputs":[],"name":"TrancheAlreadyExists","type":"error"},{"inputs":[],"name":"Unauthorized","type":"error"},{"inputs":[],"name":"Unauthorized","type":"error"},{"inputs":[],"name":"ZeroAmount","type":"error"},{"inputs":[],"name":"ZeroShares","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"creditor","type":"address"},{"indexed":false,"internalType":"uint256","name":"startDebt","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"initiationReward","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"terminationReward","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"penalty","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"badDebt","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"surplus","type":"uint256"}],"name":"AuctionFinished","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"creditor","type":"address"},{"indexed":false,"internalType":"uint128","name":"openDebt","type":"uint128"}],"name":"AuctionStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"by","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"},{"indexed":true,"internalType":"bytes3","name":"referrer","type":"bytes3"}],"name":"Borrow","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"beneficiary","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"CreditApproval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"newGuardian","type":"address"}],"name":"GuardianChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"interest","type":"uint256"}],"name":"InterestSynced","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"tranche","type":"address"},{"indexed":true,"internalType":"uint8","name":"trancheIndex","type":"uint8"},{"indexed":false,"internalType":"uint16","name":"interestWeight","type":"uint16"}],"name":"InterestWeightTrancheUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint16","name":"liquidationWeight","type":"uint16"}],"name":"LiquidationWeightTrancheUpdated","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":[{"indexed":false,"internalType":"bool","name":"repayPauseFlagsUpdated","type":"bool"},{"indexed":false,"internalType":"bool","name":"withdrawPauseFlagsUpdated","type":"bool"},{"indexed":false,"internalType":"bool","name":"borrowPauseFlagsUpdated","type":"bool"},{"indexed":false,"internalType":"bool","name":"depositPauseFlagsUpdated","type":"bool"},{"indexed":false,"internalType":"bool","name":"liquidationPauseFlagsUpdated","type":"bool"}],"name":"PauseFlagsUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"totalDebt","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"totalLiquidity","type":"uint256"},{"indexed":false,"internalType":"uint80","name":"interestRate","type":"uint80"}],"name":"PoolStateUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Repay","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"riskManager","type":"address"}],"name":"RiskManagerUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"tranche","type":"address"}],"name":"TranchePopped","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint16","name":"interestWeight","type":"uint16"},{"indexed":false,"internalType":"uint16","name":"liquidationWeight","type":"uint16"}],"name":"TreasuryWeightsUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"accountVersion","type":"uint256"},{"indexed":false,"internalType":"bool","name":"valid","type":"bool"}],"name":"ValidAccountVersionsUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Withdraw","type":"event"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tranche","type":"address"},{"internalType":"uint16","name":"interestWeight_","type":"uint16"}],"name":"addTranche","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"beneficiary","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"account","type":"address"}],"name":"approveBeneficiary","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"asset","outputs":[{"internalType":"contract ERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"startDebt","type":"uint256"},{"internalType":"uint256","name":"minimumMargin_","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"bidder","type":"address"}],"name":"auctionRepay","outputs":[{"internalType":"bool","name":"earlyTerminate","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"bytes3","name":"referrer","type":"bytes3"}],"name":"borrow","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"borrowPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"calcUnrealisedDebt","outputs":[{"internalType":"uint256","name":"unrealisedDebt","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"guardian_","type":"address"}],"name":"changeGuardian","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"closeMarginAccount","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"convertToAssets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"convertToShares","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"creditAllowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"deposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"from","type":"address"}],"name":"depositInLendingPool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"depositPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"trancheIndex","type":"uint256"},{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"donateToTranche","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountBorrowed","type":"uint256"},{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"actionTarget","type":"address"},{"internalType":"bytes","name":"actionData","type":"bytes"},{"internalType":"bytes3","name":"referrer","type":"bytes3"}],"name":"flashAction","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"callbackData","type":"bytes"}],"name":"flashActionCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getInterestRateConfig","outputs":[{"internalType":"uint72","name":"","type":"uint72"},{"internalType":"uint72","name":"","type":"uint72"},{"internalType":"uint72","name":"","type":"uint72"},{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLiquidationParameters","outputs":[{"internalType":"uint16","name":"","type":"uint16"},{"internalType":"uint16","name":"","type":"uint16"},{"internalType":"uint16","name":"","type":"uint16"},{"internalType":"uint16","name":"","type":"uint16"},{"internalType":"uint80","name":"","type":"uint80"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"getOpenPosition","outputs":[{"internalType":"uint256","name":"openPosition","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"guardian","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"interestRate","outputs":[{"internalType":"uint80","name":"","type":"uint80"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"isValidVersion","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"liquidationPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner_","type":"address"}],"name":"liquidityOf","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner_","type":"address"}],"name":"liquidityOfAndSync","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"maxDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"maxMint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"maxRedeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"maxWithdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"mint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"accountVersion","type":"uint256"}],"name":"openMarginAccount","outputs":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"address","name":"numeraire","type":"address"},{"internalType":"address","name":"liquidator_","type":"address"},{"internalType":"uint256","name":"minimumMargin_","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"originationFee","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"pauseTimestamp","outputs":[{"internalType":"uint96","name":"","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"previewDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"previewMint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"previewRedeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"previewWithdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"redeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"account","type":"address"}],"name":"repay","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"repayPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"riskManager","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"accountVersion","type":"uint256"},{"internalType":"bool","name":"valid","type":"bool"}],"name":"setAccountVersion","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint72","name":"baseRatePerYear_","type":"uint72"},{"internalType":"uint72","name":"lowSlopePerYear_","type":"uint72"},{"internalType":"uint72","name":"highSlopePerYear_","type":"uint72"},{"internalType":"uint16","name":"utilisationThreshold_","type":"uint16"}],"name":"setInterestParameters","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"uint16","name":"interestWeight_","type":"uint16"}],"name":"setInterestWeightTranche","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"initiationWeight_","type":"uint16"},{"internalType":"uint16","name":"penaltyWeight_","type":"uint16"},{"internalType":"uint16","name":"terminationWeight_","type":"uint16"},{"internalType":"uint16","name":"minRewardWeight_","type":"uint16"},{"internalType":"uint80","name":"maxReward_","type":"uint80"}],"name":"setLiquidationParameters","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"liquidationWeight","type":"uint16"}],"name":"setLiquidationWeightTranche","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint96","name":"minimumMargin_","type":"uint96"}],"name":"setMinimumMargin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"originationFee_","type":"uint8"}],"name":"setOriginationFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"riskManager_","type":"address"}],"name":"setRiskManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"treasury_","type":"address"}],"name":"setTreasury","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"interestWeight_","type":"uint16"},{"internalType":"uint16","name":"liquidationWeight","type":"uint16"}],"name":"setTreasuryWeights","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"startDebt","type":"uint256"},{"internalType":"uint256","name":"minimumMargin_","type":"uint256"},{"internalType":"address","name":"terminator","type":"address"}],"name":"settleLiquidationHappyFlow","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"startDebt","type":"uint256"},{"internalType":"uint256","name":"minimumMargin_","type":"uint256"},{"internalType":"address","name":"terminator","type":"address"}],"name":"settleLiquidationUnhappyFlow","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"skim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"initiator","type":"address"},{"internalType":"uint256","name":"minimumMargin_","type":"uint256"}],"name":"startLiquidation","outputs":[{"internalType":"uint256","name":"startDebt","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalAssets","outputs":[{"internalType":"uint256","name":"totalDebt","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalLiquidity","outputs":[{"internalType":"uint256","name":"totalLiquidity_","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"repayPaused_","type":"bool"},{"internalType":"bool","name":"withdrawPaused_","type":"bool"},{"internalType":"bool","name":"borrowPaused_","type":"bool"},{"internalType":"bool","name":"depositPaused_","type":"bool"},{"internalType":"bool","name":"liquidationPaused_","type":"bool"}],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"updateInterestRate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"withdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"withdrawFromLendingPool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}]