Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"contracts/token/CapToken.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.28;
import { Vault } from "../vault/Vault.sol";
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
/// @title Cap Token
/// @author kexley, Cap Labs
/// @notice Token representing the basket of underlying assets
contract CapToken is UUPSUpgradeable, Vault {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
/// @notice Initialize the Cap token
/// @param _name Name of the cap token
/// @param _symbol Symbol of the cap token
/// @param _accessControl Access controller
/// @param _feeAuction Fee auction address
/// @param _oracle Oracle address
/// @param _assets Asset addresses to mint Cap token with
/// @param _insuranceFund Insurance fund
function initialize(
string memory _name,
string memory _symbol,
address _accessControl,
address _feeAuction,
address _oracle,
address[] calldata _assets,
address _insuranceFund
) external initializer {
__Vault_init(_name, _symbol, _accessControl, _feeAuction, _oracle, _assets, _insuranceFund);
__UUPSUpgradeable_init();
}
/// @inheritdoc UUPSUpgradeable
function _authorizeUpgrade(address) internal view override checkAccess(bytes4(0)) { }
}
"
},
"contracts/vault/Vault.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.28;
import { Access } from "../access/Access.sol";
import { IMinter } from "../interfaces/IMinter.sol";
import { IVault } from "../interfaces/IVault.sol";
import { VaultStorageUtils } from "../storage/VaultStorageUtils.sol";
import { FractionalReserve } from "./FractionalReserve.sol";
import { Minter } from "./Minter.sol";
import { VaultLogic } from "./libraries/VaultLogic.sol";
import {
ERC20PermitUpgradeable
} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";
import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
/// @title Vault for storing the backing for cTokens
/// @author kexley, Cap Labs
/// @notice Tokens are supplied by cToken minters and borrowed by covered agents
/// @dev Supplies, borrows and utilization rates are tracked. Interest rates should be computed and
/// charged on the external contracts, only the principle amount is counted on this contract.
abstract contract Vault is
IVault,
ERC20PermitUpgradeable,
PausableUpgradeable,
Access,
Minter,
FractionalReserve,
VaultStorageUtils
{
using EnumerableSet for EnumerableSet.AddressSet;
/// @inheritdoc IVault
function mint(address _asset, uint256 _amountIn, uint256 _minAmountOut, address _receiver, uint256 _deadline)
external
whenNotPaused
returns (uint256 amountOut)
{
uint256 fee;
uint256 remainingMintCapacity = getRemainingMintCapacity(_asset);
if (_amountIn > remainingMintCapacity) _amountIn = remainingMintCapacity;
(amountOut, fee) = Minter.getMintAmount(_asset, _amountIn);
VaultLogic.mint(
getVaultStorage(),
MintBurnParams({
asset: _asset,
amountIn: _amountIn,
amountOut: amountOut,
minAmountOut: _minAmountOut,
receiver: _receiver,
deadline: _deadline,
fee: fee
})
);
_mint(_receiver, amountOut);
if (fee > 0) _mint(getVaultStorage().insuranceFund, fee);
}
/// @inheritdoc IVault
function burn(address _asset, uint256 _amountIn, uint256 _minAmountOut, address _receiver, uint256 _deadline)
external
whenNotPaused
returns (uint256 amountOut)
{
uint256 fee;
(amountOut, fee) = getBurnAmount(_asset, _amountIn);
_burn(msg.sender, _amountIn);
divest(_asset, amountOut + fee);
VaultLogic.burn(
getVaultStorage(),
MintBurnParams({
asset: _asset,
amountIn: _amountIn,
amountOut: amountOut,
minAmountOut: _minAmountOut,
receiver: _receiver,
deadline: _deadline,
fee: fee
})
);
}
/// @inheritdoc IVault
function redeem(uint256 _amountIn, uint256[] calldata _minAmountsOut, address _receiver, uint256 _deadline)
external
whenNotPaused
returns (uint256[] memory amountsOut)
{
uint256[] memory fees;
(amountsOut, fees) = getRedeemAmount(_amountIn);
_burn(msg.sender, _amountIn);
uint256[] memory totalDivestAmounts = new uint256[](amountsOut.length);
for (uint256 i; i < amountsOut.length; i++) {
totalDivestAmounts[i] = amountsOut[i] + fees[i];
}
divestMany(assets(), totalDivestAmounts);
VaultLogic.redeem(
getVaultStorage(),
RedeemParams({
amountIn: _amountIn,
amountsOut: amountsOut,
minAmountsOut: _minAmountsOut,
receiver: _receiver,
deadline: _deadline,
fees: fees
})
);
}
/// @inheritdoc IVault
function borrow(address _asset, uint256 _amount, address _receiver)
external
whenNotPaused
checkAccess(this.borrow.selector)
{
divest(_asset, _amount);
VaultLogic.borrow(getVaultStorage(), BorrowParams({ asset: _asset, amount: _amount, receiver: _receiver }));
}
/// @inheritdoc IVault
function repay(address _asset, uint256 _amount) external whenNotPaused checkAccess(this.repay.selector) {
VaultLogic.repay(getVaultStorage(), RepayParams({ asset: _asset, amount: _amount }));
}
/// @inheritdoc IVault
function addAsset(address _asset) external checkAccess(this.addAsset.selector) {
VaultLogic.addAsset(getVaultStorage(), _asset);
}
/// @inheritdoc IVault
function removeAsset(address _asset) external checkAccess(this.removeAsset.selector) {
VaultLogic.removeAsset(getVaultStorage(), _asset);
}
/// @inheritdoc IVault
function pauseAsset(address _asset) external checkAccess(this.pauseAsset.selector) {
VaultLogic.pause(getVaultStorage(), _asset);
}
/// @inheritdoc IVault
function unpauseAsset(address _asset) external checkAccess(this.unpauseAsset.selector) {
VaultLogic.unpause(getVaultStorage(), _asset);
}
/// @inheritdoc IVault
function pauseProtocol() external checkAccess(this.pauseProtocol.selector) {
_pause();
}
/// @inheritdoc IVault
function unpauseProtocol() external checkAccess(this.unpauseProtocol.selector) {
_unpause();
}
/// @inheritdoc IVault
function setInsuranceFund(address _insuranceFund) external checkAccess(this.setInsuranceFund.selector) {
VaultLogic.setInsuranceFund(getVaultStorage(), _insuranceFund);
}
/// @inheritdoc IVault
function rescueERC20(address _asset, address _receiver) external checkAccess(this.rescueERC20.selector) {
VaultLogic.rescueERC20(getVaultStorage(), getFractionalReserveStorage(), _asset, _receiver);
}
/// @inheritdoc IVault
function assets() public view returns (address[] memory assetList) {
assetList = getVaultStorage().assets.values();
}
/// @inheritdoc IVault
function totalSupplies(address _asset) public view returns (uint256 _totalSupply) {
_totalSupply = getVaultStorage().totalSupplies[_asset];
}
/// @inheritdoc IVault
function totalBorrows(address _asset) external view returns (uint256 totalBorrow) {
totalBorrow = getVaultStorage().totalBorrows[_asset];
}
/// @inheritdoc IVault
function paused(address _asset) external view returns (bool isPaused) {
isPaused = getVaultStorage().paused[_asset];
}
/// @inheritdoc IVault
function availableBalance(address _asset) external view returns (uint256 amount) {
amount = VaultLogic.availableBalance(getVaultStorage(), _asset);
}
/// @inheritdoc IVault
function utilization(address _asset) external view returns (uint256 ratio) {
ratio = VaultLogic.utilization(getVaultStorage(), _asset);
}
/// @inheritdoc IVault
function currentUtilizationIndex(address _asset) external view returns (uint256 index) {
index = VaultLogic.currentUtilizationIndex(getVaultStorage(), _asset);
}
/// @inheritdoc IVault
function insuranceFund() external view returns (address) {
return getVaultStorage().insuranceFund;
}
/// @inheritdoc IMinter
function getMintAmount(address _asset, uint256 _amountIn)
public
view
override
returns (uint256 amountOut, uint256 fee)
{
uint256 remainingMintCapacity = getRemainingMintCapacity(_asset);
if (_amountIn > remainingMintCapacity) _amountIn = remainingMintCapacity;
(amountOut, fee) = Minter.getMintAmount(_asset, _amountIn);
}
/// @inheritdoc IVault
function getRemainingMintCapacity(address _asset) public view returns (uint256 remainingMintCapacity) {
uint256 totalSupply = totalSupplies(_asset);
uint256 cap = depositCap(_asset);
if (cap > totalSupply) remainingMintCapacity = cap - totalSupply;
}
/// @dev Initialize the assets
/// @param _name Name of the cap token
/// @param _symbol Symbol of the cap token
/// @param _accessControl Access control address
/// @param _feeAuction Fee auction address
/// @param _oracle Oracle address
/// @param _assets Asset addresses
/// @param _insuranceFund Insurance fund
function __Vault_init(
string memory _name,
string memory _symbol,
address _accessControl,
address _feeAuction,
address _oracle,
address[] calldata _assets,
address _insuranceFund
) internal onlyInitializing {
__ERC20_init(_name, _symbol);
__ERC20Permit_init(_name);
__Access_init(_accessControl);
__FractionalReserve_init(_feeAuction);
__Minter_init(_oracle);
__Vault_init_unchained(_assets, _insuranceFund);
}
/// @dev Initialize unchained
/// @param _assets Asset addresses
/// @param _insuranceFund Insurance fund
function __Vault_init_unchained(address[] calldata _assets, address _insuranceFund) internal onlyInitializing {
VaultStorage storage $ = getVaultStorage();
uint256 length = _assets.length;
for (uint256 i; i < length; ++i) {
$.assets.add(_assets[i]);
}
$.insuranceFund = _insuranceFund;
}
}
"
},
"node_modules/@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (proxy/utils/UUPSUpgradeable.sol)
pragma solidity ^0.8.22;
import {IERC1822Proxiable} from "@openzeppelin/contracts/interfaces/draft-IERC1822.sol";
import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
import {Initializable} from "./Initializable.sol";
/**
* @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an
* {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.
*
* A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is
* reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing
* `UUPSUpgradeable` with a custom implementation of upgrades.
*
* The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.
*/
abstract contract UUPSUpgradeable is Initializable, IERC1822Proxiable {
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
address private immutable __self = address(this);
/**
* @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgradeTo(address)`
* and `upgradeToAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called,
* while `upgradeToAndCall` will invoke the `receive` function if the second argument is the empty byte string.
* If the getter returns `"5.0.0"`, only `upgradeToAndCall(address,bytes)` is present, and the second argument must
* be the empty byte string if no function should be called, making it impossible to invoke the `receive` function
* during an upgrade.
*/
string public constant UPGRADE_INTERFACE_VERSION = "5.0.0";
/**
* @dev The call is from an unauthorized context.
*/
error UUPSUnauthorizedCallContext();
/**
* @dev The storage `slot` is unsupported as a UUID.
*/
error UUPSUnsupportedProxiableUUID(bytes32 slot);
/**
* @dev Check that the execution is being performed through a delegatecall call and that the execution context is
* a proxy contract with an implementation (as defined in ERC-1967) pointing to self. This should only be the case
* for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a
* function through ERC-1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to
* fail.
*/
modifier onlyProxy() {
_checkProxy();
_;
}
/**
* @dev Check that the execution is not being performed through a delegate call. This allows a function to be
* callable on the implementing contract but not through proxies.
*/
modifier notDelegated() {
_checkNotDelegated();
_;
}
function __UUPSUpgradeable_init() internal onlyInitializing {
}
function __UUPSUpgradeable_init_unchained() internal onlyInitializing {
}
/**
* @dev Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the
* implementation. It is used to validate the implementation's compatibility when performing an upgrade.
*
* IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
* bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
* function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.
*/
function proxiableUUID() external view virtual notDelegated returns (bytes32) {
return ERC1967Utils.IMPLEMENTATION_SLOT;
}
/**
* @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call
* encoded in `data`.
*
* Calls {_authorizeUpgrade}.
*
* Emits an {Upgraded} event.
*
* @custom:oz-upgrades-unsafe-allow-reachable delegatecall
*/
function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy {
_authorizeUpgrade(newImplementation);
_upgradeToAndCallUUPS(newImplementation, data);
}
/**
* @dev Reverts if the execution is not performed via delegatecall or the execution
* context is not of a proxy with an ERC-1967 compliant implementation pointing to self.
*/
function _checkProxy() internal view virtual {
if (
address(this) == __self || // Must be called through delegatecall
ERC1967Utils.getImplementation() != __self // Must be called through an active proxy
) {
revert UUPSUnauthorizedCallContext();
}
}
/**
* @dev Reverts if the execution is performed via delegatecall.
* See {notDelegated}.
*/
function _checkNotDelegated() internal view virtual {
if (address(this) != __self) {
// Must not be called through delegatecall
revert UUPSUnauthorizedCallContext();
}
}
/**
* @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by
* {upgradeToAndCall}.
*
* Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.
*
* ```solidity
* function _authorizeUpgrade(address) internal onlyOwner {}
* ```
*/
function _authorizeUpgrade(address newImplementation) internal virtual;
/**
* @dev Performs an implementation upgrade with a security check for UUPS proxies, and additional setup call.
*
* As a security check, {proxiableUUID} is invoked in the new implementation, and the return value
* is expected to be the implementation slot in ERC-1967.
*
* Emits an {IERC1967-Upgraded} event.
*/
function _upgradeToAndCallUUPS(address newImplementation, bytes memory data) private {
try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
if (slot != ERC1967Utils.IMPLEMENTATION_SLOT) {
revert UUPSUnsupportedProxiableUUID(slot);
}
ERC1967Utils.upgradeToAndCall(newImplementation, data);
} catch {
// The implementation is not UUPS
revert ERC1967Utils.ERC1967InvalidImplementation(newImplementation);
}
}
}
"
},
"contracts/access/Access.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.28;
import { IAccess } from "../interfaces/IAccess.sol";
import { IAccessControl } from "../interfaces/IAccessControl.sol";
import { AccessStorageUtils } from "../storage/AccessStorageUtils.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
/// @title Access
/// @author kexley, Cap Labs
/// @notice Inheritable access
abstract contract Access is IAccess, Initializable, AccessStorageUtils {
/// @dev Check caller has permissions for a function, revert if call is not allowed
/// @param _selector Function selector
modifier checkAccess(bytes4 _selector) {
_checkAccess(_selector);
_;
}
/// @dev Initialize the access control address
/// @param _accessControl Access control address
function __Access_init(address _accessControl) internal onlyInitializing {
__Access_init_unchained(_accessControl);
}
/// @dev Initialize unchained
/// @param _accessControl Access control address
function __Access_init_unchained(address _accessControl) internal onlyInitializing {
getAccessStorage().accessControl = _accessControl;
}
/// @dev Check caller has access to a function, revert overwise
/// @param _selector Function selector
function _checkAccess(bytes4 _selector) internal view {
bool hasAccess =
IAccessControl(getAccessStorage().accessControl).checkAccess(_selector, address(this), msg.sender);
if (!hasAccess) revert AccessDenied();
}
}
"
},
"contracts/interfaces/IMinter.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.28;
/// @title IMinter
/// @author kexley, Cap Labs
/// @notice Interface for Minter contract
interface IMinter {
/// @dev Minter storage
/// @param oracle Oracle address
/// @param redeemFee Redeem fee
/// @param fees Fees for each asset
/// @param whitelist Whitelist for users
/// @param depositCap Deposit cap for each asset
struct MinterStorage {
address oracle;
uint256 redeemFee;
mapping(address => FeeData) fees;
mapping(address => bool) whitelist;
mapping(address => uint256) depositCap;
}
/// @dev Fee data set for an asset in a vault
/// @param minMintFee Minimum mint fee
/// @param slope0 Slope 0
/// @param slope1 Slope 1
/// @param mintKinkRatio Mint kink ratio
/// @param burnKinkRatio Burn kink ratio
/// @param optimalRatio Optimal ratio
struct FeeData {
uint256 minMintFee;
uint256 slope0;
uint256 slope1;
uint256 mintKinkRatio;
uint256 burnKinkRatio;
uint256 optimalRatio;
}
/// @dev Parameters for applying fee slopes
/// @param mint True if applying to mint, false if applying to burn
/// @param amount Amount of asset to apply fee to
/// @param ratio Ratio of fee to apply
struct FeeSlopeParams {
bool mint;
uint256 amount;
uint256 ratio;
}
/// @dev Parameters for minting or burning
/// @param mint True if minting, false if burning
/// @param asset Asset address
/// @param amount Amount of asset to mint or burn
struct AmountOutParams {
bool mint;
address asset;
uint256 amount;
}
/// @dev Parameters for redeeming
/// @param amount Amount of cap token to redeem
struct RedeemAmountOutParams {
uint256 amount;
}
/// @dev Fee data set for an asset in a vault
event SetFeeData(address asset, FeeData feeData);
/// @dev Redeem fee set
event SetRedeemFee(uint256 redeemFee);
/// @dev Whitelist set
event SetWhitelist(address user, bool whitelisted);
/// @dev Deposit cap set
event SetDepositCap(address asset, uint256 cap);
/// @dev Invalid minimum mint fee
error InvalidMinMintFee();
/// @dev Invalid mint kink ratio
error InvalidMintKinkRatio();
/// @dev Invalid burn kink ratio
error InvalidBurnKinkRatio();
/// @dev Invalid optimal ratio
error InvalidOptimalRatio();
/// @notice Get the fee data for an asset
/// @param _asset Asset address
/// @return feeData Fee data for the asset
function getFeeData(address _asset) external view returns (FeeData memory feeData);
/// @notice Set the allocation slopes and ratios for an asset
/// @param _asset Asset address
/// @param _feeData Fee slopes and ratios for the asset in the vault
function setFeeData(address _asset, FeeData calldata _feeData) external;
/// @notice Get the redeem fee
/// @return redeemFee Redeem fee amount
function getRedeemFee() external view returns (uint256 redeemFee);
/// @notice Set the redeem fee
/// @param _redeemFee Redeem fee amount
function setRedeemFee(uint256 _redeemFee) external;
/// @notice Set the whitelist for a user
/// @param _user User address
/// @param _whitelisted Whitelist state
function setWhitelist(address _user, bool _whitelisted) external;
/// @notice Set the deposit cap for an asset
/// @param _asset Asset address
/// @param _cap Deposit cap for the asset
function setDepositCap(address _asset, uint256 _cap) external;
/// @notice Get the mint amount for a given asset
/// @param _asset Asset address
/// @param _amountIn Amount of asset to use
/// @return amountOut Amount minted
/// @return fee Fee applied
function getMintAmount(address _asset, uint256 _amountIn) external view returns (uint256 amountOut, uint256 fee);
/// @notice Get the burn amount for a given asset
/// @param _asset Asset address to withdraw
/// @param _amountIn Amount of cap token to burn
/// @return amountOut Amount of the asset withdrawn
/// @return fee Fee applied
function getBurnAmount(address _asset, uint256 _amountIn) external view returns (uint256 amountOut, uint256 fee);
/// @notice Get the redeem amount
/// @param _amountIn Amount of cap token to burn
/// @return amountsOut Amounts of assets to be withdrawn
/// @return redeemFees Amounts of redeem fees to be applied
function getRedeemAmount(uint256 _amountIn)
external
view
returns (uint256[] memory amountsOut, uint256[] memory redeemFees);
/// @notice Is user whitelisted
/// @param _user User address
/// @return isWhitelisted Whitelist state
function whitelisted(address _user) external view returns (bool isWhitelisted);
/// @notice Get the deposit cap for an asset
/// @param _asset Asset address
/// @return cap Deposit cap for the asset
function depositCap(address _asset) external view returns (uint256 cap);
}
"
},
"contracts/interfaces/IVault.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.28;
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
/// @title Vault interface for storing the backing for cTokens
/// @author kexley, Cap Labs
/// @notice Interface for the Vault contract which handles supplies, borrows and utilization tracking
interface IVault {
/// @dev Storage for the vault
/// @param assets List of assets
/// @param totalSupplies Total supplies of an asset
/// @param totalBorrows Total borrows of an asset
/// @param utilizationIndex Utilization index of an asset
/// @param lastUpdate Last update time of an asset
/// @param paused Pause state of an asset
/// @param insuranceFund Insurance fund address
struct VaultStorage {
EnumerableSet.AddressSet assets;
mapping(address => uint256) totalSupplies;
mapping(address => uint256) totalBorrows;
mapping(address => uint256) utilizationIndex;
mapping(address => uint256) lastUpdate;
mapping(address => bool) paused;
address insuranceFund;
}
/// @dev Parameters for minting or burning
/// @param asset Asset to mint or burn
/// @param amountIn Amount of asset to use in the minting or burning
/// @param amountOut Amount of cap token to mint or burn
/// @param minAmountOut Minimum amount to mint or burn
/// @param receiver Receiver of the minting or burning
/// @param deadline Deadline of the tx
/// @param fee Fee paid to the insurance fund
struct MintBurnParams {
address asset;
uint256 amountIn;
uint256 amountOut;
uint256 minAmountOut;
address receiver;
uint256 deadline;
uint256 fee;
}
/// @dev Parameters for redeeming
/// @param amountIn Amount of cap token to burn
/// @param amountsOut Amounts of assets to withdraw
/// @param minAmountsOut Minimum amounts of assets to withdraw
/// @param receiver Receiver of the withdrawal
/// @param deadline Deadline of the tx
/// @param fees Fees paid to the insurance fund
struct RedeemParams {
uint256 amountIn;
uint256[] amountsOut;
uint256[] minAmountsOut;
address receiver;
uint256 deadline;
uint256[] fees;
}
/// @dev Parameters for borrowing
/// @param asset Asset to borrow
/// @param amount Amount of asset to borrow
/// @param receiver Receiver of the borrow
struct BorrowParams {
address asset;
uint256 amount;
address receiver;
}
/// @dev Parameters for repaying
/// @param asset Asset to repay
/// @param amount Amount of asset to repay
struct RepayParams {
address asset;
uint256 amount;
}
/// @notice Mint the cap token using an asset
/// @dev This contract must have approval to move asset from msg.sender
/// @dev The amount in is capped by the deposit cap of the asset
/// @param _asset Whitelisted asset to deposit
/// @param _amountIn Amount of asset to use in the minting
/// @param _minAmountOut Minimum amount to mint
/// @param _receiver Receiver of the minting
/// @param _deadline Deadline of the tx
function mint(address _asset, uint256 _amountIn, uint256 _minAmountOut, address _receiver, uint256 _deadline)
external
returns (uint256 amountOut);
/// @notice Burn the cap token for an asset
/// @dev Asset is withdrawn from the reserve or divested from the underlying vault
/// @param _asset Asset to withdraw
/// @param _amountIn Amount of cap token to burn
/// @param _minAmountOut Minimum amount out to receive
/// @param _receiver Receiver of the withdrawal
/// @param _deadline Deadline of the tx
function burn(address _asset, uint256 _amountIn, uint256 _minAmountOut, address _receiver, uint256 _deadline)
external
returns (uint256 amountOut);
/// @notice Redeem the Cap token for a bundle of assets
/// @dev Assets are withdrawn from the reserve or divested from the underlying vault
/// @param _amountIn Amount of Cap token to burn
/// @param _minAmountsOut Minimum amounts of assets to withdraw
/// @param _receiver Receiver of the withdrawal
/// @param _deadline Deadline of the tx
/// @return amountsOut Amount of assets withdrawn
function redeem(uint256 _amountIn, uint256[] calldata _minAmountsOut, address _receiver, uint256 _deadline)
external
returns (uint256[] memory amountsOut);
/// @notice Borrow an asset
/// @dev Whitelisted agents can borrow any amount, LTV is handled by Agent contracts
/// @param _asset Asset to borrow
/// @param _amount Amount of asset to borrow
/// @param _receiver Receiver of the borrow
function borrow(address _asset, uint256 _amount, address _receiver) external;
/// @notice Repay an asset
/// @param _asset Asset to repay
/// @param _amount Amount of asset to repay
function repay(address _asset, uint256 _amount) external;
/// @notice Add an asset to the vault list
/// @param _asset Asset address
function addAsset(address _asset) external;
/// @notice Remove an asset from the vault list
/// @param _asset Asset address
function removeAsset(address _asset) external;
/// @notice Pause an asset
/// @param _asset Asset address
function pauseAsset(address _asset) external;
/// @notice Unpause an asset
/// @param _asset Asset address
function unpauseAsset(address _asset) external;
/// @notice Pause all protocol operations
function pauseProtocol() external;
/// @notice Unpause all protocol operations
function unpauseProtocol() external;
/// @notice Set the insurance fund
/// @param _insuranceFund Insurance fund address
function setInsuranceFund(address _insuranceFund) external;
/// @notice Rescue an unsupported asset
/// @param _asset Asset to rescue
/// @param _receiver Receiver of the rescue
function rescueERC20(address _asset, address _receiver) external;
/// @notice Get the list of assets supported by the vault
/// @return assetList List of assets
function assets() external view returns (address[] memory assetList);
/// @notice Get the total supplies of an asset
/// @param _asset Asset address
/// @return totalSupply Total supply
function totalSupplies(address _asset) external view returns (uint256 totalSupply);
/// @notice Get the total borrows of an asset
/// @param _asset Asset address
/// @return totalBorrow Total borrow
function totalBorrows(address _asset) external view returns (uint256 totalBorrow);
/// @notice Get the pause state of an asset
/// @param _asset Asset address
/// @return isPaused Pause state
function paused(address _asset) external view returns (bool isPaused);
/// @notice Available balance to borrow
/// @param _asset Asset to borrow
/// @return amount Amount available
function availableBalance(address _asset) external view returns (uint256 amount);
/// @notice Utilization rate of an asset
/// @dev Utilization scaled by 1e27
/// @param _asset Utilized asset
/// @return ratio Utilization ratio
function utilization(address _asset) external view returns (uint256 ratio);
/// @notice Up to date cumulative utilization index of an asset
/// @dev Utilization scaled by 1e27
/// @param _asset Utilized asset
/// @return index Utilization ratio index
function currentUtilizationIndex(address _asset) external view returns (uint256 index);
/// @notice Get the insurance fund
/// @return insuranceFund Insurance fund
function insuranceFund() external view returns (address);
/// @notice Get the remaining mint capacity of an asset
/// @param _asset Asset address
/// @return remainingMintCapacity Remaining mint capacity
function getRemainingMintCapacity(address _asset) external view returns (uint256 remainingMintCapacity);
}
"
},
"contracts/storage/VaultStorageUtils.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.28;
import { IVault } from "../interfaces/IVault.sol";
/// @title VaultStorageUtils
/// @author kexley, Cap Labs
/// @notice Storage utilities for Vault contract
abstract contract VaultStorageUtils {
/// @dev keccak256(abi.encode(uint256(keccak256("cap.storage.Vault")) - 1)) & ~bytes32(uint256(0xff))
bytes32 constant VaultStorageLocation = 0xe912a1b0cc7579bc5827e495c2ce52587bc3871751e3281fc5599b38c3bfc400;
/// @notice Get vault storage
/// @return $ Storage pointer
function getVaultStorage() internal pure returns (IVault.VaultStorage storage $) {
assembly {
$.slot := VaultStorageLocation
}
}
}
"
},
"contracts/vault/FractionalReserve.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import { Access } from "../access/Access.sol";
import { IFractionalReserve } from "../interfaces/IFractionalReserve.sol";
import { FractionalReserveStorageUtils } from "../storage/FractionalReserveStorageUtils.sol";
import { FractionalReserveLogic } from "./libraries/FractionalReserveLogic.sol";
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
/// @title Fractional Reserve
/// @author kexley, Cap Labs
/// @notice Idle capital is put to work in fractional reserve vaults and can be recalled when withdrawing, redeeming or borrowing.
abstract contract FractionalReserve is IFractionalReserve, Access, FractionalReserveStorageUtils {
using EnumerableSet for EnumerableSet.AddressSet;
/// @inheritdoc IFractionalReserve
function investAll(address _asset) external checkAccess(this.investAll.selector) {
FractionalReserveLogic.invest(getFractionalReserveStorage(), _asset);
}
/// @inheritdoc IFractionalReserve
function divestAll(address _asset) external checkAccess(this.divestAll.selector) {
FractionalReserveLogic.divest(getFractionalReserveStorage(), _asset);
}
/// @inheritdoc IFractionalReserve
function setFractionalReserveVault(address _asset, address _vault)
external
checkAccess(this.setFractionalReserveVault.selector)
{
FractionalReserveStorage storage $ = getFractionalReserveStorage();
FractionalReserveLogic.divest($, _asset);
FractionalReserveLogic.setFractionalReserveVault($, _asset, _vault);
}
/// @inheritdoc IFractionalReserve
function setReserve(address _asset, uint256 _reserve) external checkAccess(this.setReserve.selector) {
FractionalReserveLogic.setReserve(getFractionalReserveStorage(), _asset, _reserve);
}
/// @inheritdoc IFractionalReserve
function realizeInterest(address _asset) external {
FractionalReserveLogic.realizeInterest(getFractionalReserveStorage(), _asset);
}
/// @inheritdoc IFractionalReserve
function claimableInterest(address _asset) external view returns (uint256 interest) {
interest = FractionalReserveLogic.claimableInterest(getFractionalReserveStorage(), _asset);
}
/// @inheritdoc IFractionalReserve
function fractionalReserveVault(address _asset) external view returns (address vaultAddress) {
vaultAddress = getFractionalReserveStorage().vault[_asset];
}
/// @inheritdoc IFractionalReserve
function fractionalReserveVaults() external view returns (address[] memory vaultAddresses) {
vaultAddresses = getFractionalReserveStorage().vaults.values();
}
/// @inheritdoc IFractionalReserve
function reserve(address _asset) external view returns (uint256 reserveAmount) {
reserveAmount = getFractionalReserveStorage().reserve[_asset];
}
/// @inheritdoc IFractionalReserve
function loaned(address _asset) external view returns (uint256 loanedAmount) {
loanedAmount = getFractionalReserveStorage().loaned[_asset];
}
/// @inheritdoc IFractionalReserve
function interestReceiver() external view returns (address _interestReceiver) {
_interestReceiver = getFractionalReserveStorage().interestReceiver;
}
/// @dev Initialize unchained
/// @param _interestReceiver Interest receiver address
function __FractionalReserve_init(address _interestReceiver) internal onlyInitializing {
getFractionalReserveStorage().interestReceiver = _interestReceiver;
}
/// @dev Divest an asset from a fractional reserve vault
/// @param _asset Asset address
/// @param _amountOut Amount to divest
function divest(address _asset, uint256 _amountOut) internal {
FractionalReserveLogic.divest(getFractionalReserveStorage(), _asset, _amountOut);
}
/// @dev Divest many assets from a fractional reserve vault
/// @param _assets Assets to divest
/// @param _amountsOut Amounts to divest
function divestMany(address[] memory _assets, uint256[] memory _amountsOut) internal {
FractionalReserveStorage storage $ = getFractionalReserveStorage();
for (uint256 i; i < _assets.length; ++i) {
FractionalReserveLogic.divest($, _assets[i], _amountsOut[i]);
}
}
}
"
},
"contracts/vault/Minter.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.28;
import { Access } from "../access/Access.sol";
import { IMinter } from "../interfaces/IMinter.sol";
import { MinterStorageUtils } from "../storage/MinterStorageUtils.sol";
import { MinterLogic } from "./libraries/MinterLogic.sol";
/// @title Minter/burner for cap tokens
/// @author kexley, Cap Labs
/// @notice Cap tokens are minted or burned in exchange for collateral ratio of the backing tokens
/// @dev Dynamic fees are applied according to the allocation of assets in the basket. Increasing
/// the supply of a excessive asset or burning for an scarce asset will charge fees on a kinked
/// slope. Redeem can be used to avoid these fees by burning for the current ratio of assets.
abstract contract Minter is IMinter, Access, MinterStorageUtils {
/// @inheritdoc IMinter
function getFeeData(address _asset) external view returns (FeeData memory feeData) {
feeData = getMinterStorage().fees[_asset];
}
/// @inheritdoc IMinter
function setFeeData(address _asset, FeeData calldata _feeData) external checkAccess(this.setFeeData.selector) {
if (_feeData.minMintFee >= 0.05e27) revert InvalidMinMintFee();
if (_feeData.mintKinkRatio >= 1e27 || _feeData.mintKinkRatio == 0) revert InvalidMintKinkRatio();
if (_feeData.burnKinkRatio >= 1e27 || _feeData.burnKinkRatio == 0) revert InvalidBurnKinkRatio();
if (_feeData.optimalRatio >= 1e27 || _feeData.optimalRatio == 0) revert InvalidOptimalRatio();
if (_feeData.optimalRatio == _feeData.mintKinkRatio || _feeData.optimalRatio == _feeData.burnKinkRatio) {
revert InvalidOptimalRatio();
}
getMinterStorage().fees[_asset] = _feeData;
emit SetFeeData(_asset, _feeData);
}
/// @inheritdoc IMinter
function getRedeemFee() external view returns (uint256 redeemFee) {
redeemFee = getMinterStorage().redeemFee;
}
/// @inheritdoc IMinter
function setRedeemFee(uint256 _redeemFee) external checkAccess(this.setRedeemFee.selector) {
getMinterStorage().redeemFee = _redeemFee;
emit SetRedeemFee(_redeemFee);
}
/// @inheritdoc IMinter
function setWhitelist(address _user, bool _whitelisted) external checkAccess(this.setWhitelist.selector) {
getMinterStorage().whitelist[_user] = _whitelisted;
emit SetWhitelist(_user, _whitelisted);
}
/// @inheritdoc IMinter
function setDepositCap(address _asset, uint256 _cap) external checkAccess(this.setDepositCap.selector) {
getMinterStorage().depositCap[_asset] = _cap;
emit SetDepositCap(_asset, _cap);
}
/// @inheritdoc IMinter
function getMintAmount(address _asset, uint256 _amountIn)
public
view
virtual
returns (uint256 amountOut, uint256 fee)
{
(amountOut, fee) = MinterLogic.amountOut(
getMinterStorage(), AmountOutParams({ mint: true, asset: _asset, amount: _amountIn })
);
}
/// @inheritdoc IMinter
function getBurnAmount(address _asset, uint256 _amountIn) public view returns (uint256 amountOut, uint256 fee) {
(amountOut, fee) = MinterLogic.amountOut(
getMinterStorage(), AmountOutParams({ mint: false, asset: _asset, amount: _amountIn })
);
}
/// @inheritdoc IMinter
function getRedeemAmount(uint256 _amountIn)
public
view
returns (uint256[] memory amountsOut, uint256[] memory fees)
{
(amountsOut, fees) = MinterLogic.redeemAmountOut(
getMinterStorage(), RedeemAmountOutParams({ amount: _amountIn })
);
}
/// @inheritdoc IMinter
function whitelisted(address _user) external view returns (bool isWhitelisted) {
isWhitelisted = getMinterStorage().whitelist[_user];
}
/// @inheritdoc IMinter
function depositCap(address _asset) public view returns (uint256 cap) {
cap = getMinterStorage().depositCap[_asset];
}
/// @dev Initialize unchained
/// @param _oracle Oracle address
function __Minter_init(address _oracle) internal onlyInitializing {
getMinterStorage().oracle = _oracle;
}
}
"
},
"contracts/vault/libraries/VaultLogic.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.28;
import { IFractionalReserve } from "../../interfaces/IFractionalReserve.sol";
import { IVault } from "../../interfaces/IVault.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
/// @title Vault for storing the backing for cTokens
/// @author kexley, Cap Labs
/// @notice Tokens are supplied by cToken minters and borrowed by covered agents
/// @dev Supplies, borrows and utilization rates are tracked. Interest rates should be computed and
/// charged on the external contracts, only the principle amount is counted on this contract.
library VaultLogic {
using SafeERC20 for IERC20;
using EnumerableSet for EnumerableSet.AddressSet;
/// @dev Cap token minted
event Mint(
address indexed minter,
address receiver,
address indexed asset,
uint256 amountIn,
uint256 amountOut,
uint256 fee
);
/// @dev Cap token burned
event Burn(
address indexed burner,
address receiver,
address indexed asset,
uint256 amountIn,
uint256 amountOut,
uint256 fee
);
/// @dev Cap token redeemed
event Redeem(address indexed redeemer, address receiver, uint256 amountIn, uint256[] amountsOut, uint256[] fees);
/// @dev Borrow made
event Borrow(address indexed borrower, address indexed asset, uint256 amount);
/// @dev Repayment made
event Repay(address indexed repayer, address indexed asset, uint256 amount);
/// @dev Add asset
event AddAsset(address asset);
/// @dev Remove asset
event RemoveAsset(address asset);
/// @dev Asset paused
event PauseAsset(address asset);
/// @dev Asset unpaused
event UnpauseAsset(address asset);
/// @dev Rescue unsupported ERC20 tokens
event RescueERC20(address asset, address receiver);
/// @dev Set the insurance fund
event SetInsuranceFund(address insuranceFund);
/// @dev Timestamp is past the deadline
error PastDeadline();
/// @dev Amount out is less than required
error Slippage(address asset, uint256 amountOut, uint256 minAmountOut);
/// @dev Amount out is 0
error InvalidAmount();
/// @dev Paused assets cannot be supplied or borrowed
error AssetPaused(address asset);
/// @dev Only whitelisted assets can be supplied or borrowed
error AssetNotSupported(address asset);
/// @dev Asset is already listed
error AssetAlreadySupported(address asset);
/// @dev Asset has supplies
error AssetHasSupplies(address asset);
/// @dev Only non-supported assets can be rescued
error AssetNotRescuable(address asset);
/// @dev Invalid min amounts out as they dont match the number of assets
error InvalidMinAmountsOut();
/// @dev Insufficient reserves
error InsufficientReserves(address asset, uint256 balanceBefore, uint256 amount);
/// @dev Modifier to only allow supplies and borrows when not paused
/// @param $ Vault storage pointer
/// @param _asset Asset address
modifier whenNotPaused(IVault.VaultStorage storage $, address _asset) {
_whenNotPaused($, _asset);
_;
}
/// @dev Modifier to update the utilization index
/// @param $ Vault storage pointer
/// @param _asset Asset address
modifier updateIndex(IVault.VaultStorage storage $, address _asset) {
_updateIndex($, _asset);
_;
}
/// @notice Mint the cap token using an asset
/// @dev This contract must have approval to move asset from msg.sender
/// @param $ Vault storage pointer
/// @param params Mint parameters
function mint(IVault.VaultStorage storage $, IVault.MintBurnParams memory params)
external
whenNotPaused($, params.asset)
updateIndex($, params.asset)
{
if (params.deadline < block.timestamp) revert PastDeadline();
if (params.amountOut < params.minAmountOut) {
revert Slippage(address(this), params.amountOut, params.minAmountOut);
}
if (params.amountOut == 0) revert InvalidAmount();
$.totalSupplies[params.asset] += params.amountIn;
IERC20(params.asset).safeTransferFrom(msg.sender, address(this), params.amountIn);
emit Mint(msg.sender, params.receiver, params.asset, params.amountIn, params.amountOut, params.fee);
}
/// @notice Burn the cap token for an asset
/// @dev Can only withdraw up to the amount remaining on this contract
/// @param $ Vault storage pointer
/// @param params Burn parameters
function burn(IVault.VaultStorage storage $, IVault.MintBurnParams memory params)
external
updateIndex($, params.asset)
{
if (params.deadline < block.timestamp) revert PastDeadline();
if (params.amountOut < params.minAmountOut) {
revert Slippage(params.asset, params.amountOut, params.minAmountOut);
}
if (params.amountOut == 0) revert InvalidAmount();
_verifyBalance($, params.asset, params.amountOut + params.fee);
$.totalSupplies[params.asset] -= params.amountOut + params.fee;
IERC20(params.asset).safeTransfer(params.receiver, params.amountOut);
if (params.fee > 0) IERC20(params.asset).safeTransfer($.insuranceFund, params.fee);
emit Burn(msg.sender, params.receiver, params.asset, params.amountIn, params.amountOut, params.fee);
}
/// @notice Redeem the Cap token for a bundle of assets
/// @dev Can only withdraw up to the amount remaining on this contract
/// @param $ Vault storage pointer
/// @param params Redeem parameters
function redeem(IVault.VaultStorage storage $, IVault.RedeemParams memory params) external {
if (params.amountsOut.length != params.minAmountsOut.length) revert InvalidMinAmountsOut();
if (params.deadline < block.timestamp) revert PastDeadline();
uint256 length = $.assets.length();
for (uint256 i; i < length; ++i) {
address asset = $.assets.at(i);
if (params.amountsOut[i] < params.minAmountsOut[i]) {
revert Slippage(asset, params.amountsOut[i], params.minAmountsOut[i]);
}
_verifyBalance($, asset, params.amountsOut[i] + params.fees[i]);
_updateIndex($, asset);
$.totalSupplies[asset] -= params.amountsOut[i] + params.fees[i];
IERC20(asset).safeTransfer(params.receiver, params.amountsOut[i]);
if (params.fees[i] > 0) IERC20(asset).safeTransfer($.insuranceFund, params.fees[i]);
}
emit Redeem(msg.sender, params.receiver, params.amountIn, params.amountsOut, params.fees);
}
/// @notice Borrow an asset
/// @dev Whitelisted agents can borrow any amount, LTV is handled by Agent contracts
/// @param $ Vault storage pointer
/// @param params Borrow parameters
function borrow(IVault.VaultStorage storage $, IVault.BorrowParams memory params)
external
whenNotPaused($, params.asset)
updateIndex($, params.asset)
{
_verifyBalance($, params.asset, params.amount);
$.totalBorrows[params.asset] += params.amount;
IERC20(params.asset).safeTransfer(params.receiver, params.amount);
emit Borrow(msg.sender, params.asset, params.amount);
}
/// @notice Repay an asset
/// @param $ Vault storage pointer
/// @param params Repay parameters
function repay(IVault.VaultStorage storage $, IVault.RepayParams memory params)
external
updateIndex($, params.asset)
{
$.totalBorrows[params.asset] -= params.amount;
IERC20(params.asset).safeTransferFrom(msg.sender, address(this), params.amount);
emit Repay(msg.sender, params.asset, params.amount);
}
/// @notice Add an asset to the vault list
/// @param $ Vault storage pointer
/// @param _asset Asset address
function addAsset(IVault.VaultStorage storage $, address _asset) external {
if (!$.assets.add(_asset)) revert AssetNotSupported(_asset);
emit AddAsset(_asset);
}
/// @notice Remove an asset from the vault list
/// @param $ Vault storage pointer
/// @param _asset Asset address
function removeAsset(IVault.VaultStorage storage $, address _asset) external {
if ($.totalSupplies[_asset] > 0) revert AssetHasSupplies(_asset);
if (!$.assets.remove(_asset)) revert AssetNotSupported(_asset);
emit RemoveAsset(_asset);
}
/// @notice Pause an asset
/// @param $ Vault storage pointer
/// @param _asset Asset address
function pause(IVault.VaultStorage storage $, address _asset) external {
$.paused[_asset] = true;
emit PauseAsset(_asset);
}
/// @notice Unpause an asset
/// @param $ Vault storage pointer
/// @param _asset Asset address
function unpause(IVault.VaultStorage storage $, address _asset) external {
$.paused[_asset] = false;
emit UnpauseAsset(_asset);
}
/// @notice Set the insurance fund
/// @param $ Vault storage pointer
/// @param _insuranceFund Insurance fund address
function setInsuranceFund(IVault.VaultStorage storage $, address _insuranceFund) external {
$.insuranceFund = _insuranceFund;
emit SetInsuranceFund(_insuranceFund);
}
/// @notice Rescue an unsupported asset
/// @param $ Vault storage pointer
/// @param reserve Fractional reserve storage pointer
/// @param _asset Asset to rescue
/// @param _receiver Receiver of the rescue
function rescueERC20(
IVault.VaultStorage storage $,
IFractionalReserve.FractionalReserveStorage storage reserve,
address _asset,
address _receiver
) external {
if (_listed($, _asset) || reserve.vaults.contains(_asset)) revert AssetNotRescuable(_asset);
IERC20(_asset).safeTransfer(_receiver, IERC20(_asset).balanceOf(address(this)));
emit RescueERC20(_asset, _receiver);
}
/// @notice Calculate the available balance of an asset
/// @param $ Vault storage pointer
/// @param _asset Asset address
/// @return balance Available balance
function availableBalance(IVault.VaultStorage storage $, address _asset) public view returns (uint256 balance) {
balance = $.totalSupplies[_asset] - $.totalBorrows[_asset];
}
/// @notice Calculate the utilization ratio of an asset
/// @dev Returns the ratio of borrowed assets to total supply, scaled to ray (1e27)
/// @param $ Vault storage pointer
/// @param _asset Asset address
/// @return ratio Utilization ratio in ray (1e27)
function utilization(IVault.VaultStorage storage $, address _asset) public view returns (uint256 ratio) {
ratio = $.totalSupplies[_asset] != 0 ? $.totalBorrows[_asset] * 1e27 / $.totalSupplies[_asset] : 0;
}
/// @notice Up to date cumulative utilization index of an asset
/// @dev Utilization and index are both scaled in ray (1e27)
/// @param $ Vault storage pointer
/// @param _asset Utilized asset
/// @return index Utilization ratio index in ray (1e27)
function currentUtilizationIndex(IVault.VaultStorage storage $, address _asset)
external
view
returns (uint256 index)
{
index = $.utilizationIndex[_asset] + (utilization($, _asset) * (block.timestamp - $.lastUpdate[_asset]));
}
/// @dev Validate that an asset is listed
/// @param $ Vault storage pointer
/// @param _asset Asset to check
/// @return isListed Asset is listed or not
function _listed(IVault.VaultStorage storage $, address _asset) internal view returns (bool isListed) {
isListed = $.assets.contains(_asset);
}
/// @dev Verify that an asset has enough balance
/// @param $ Vault storage pointer
/// @param _asset Asset address
/// @param _amount Amount to verify
function _verifyBalance(IVault.VaultStorage storage $, address _asset, uint256 _amount) internal view {
uint256 balance = availableBalance($, _asset);
if (balance < _amount) {
revert InsufficientReserves(_asset, balance, _amount);
}
}
/// @dev Only allow supplies and borrows when not paused
/// @param $ Vault storage pointer
/// @param _asset Asset address
function _whenNotPaused(IVault.VaultStorage storage $, address _asset) private view {
if ($.paused[_asset]) revert AssetPaused(_asset);
}
/// @dev Update the cumulative utilization index of an asset
/// @param $ Vault storage pointer
/// @param _asset Utilized asset
function _updateIndex(IVault.VaultStorage storage $, address _asset) internal {
if (!_listed($, _asset)) revert AssetNotSupported(_asset);
$.utilizationIndex[_asset] += utilization($, _asset) * (block.timestamp - $.lastUpdate[_asset]);
$.lastUpdate[_asset] = block.timestamp;
}
}
"
},
"node_modules/@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/ERC20Permit.sol)
pragma solidity ^0.8.20;
import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
import {ERC20Upgradeable} from "../ERC20Upgradeable.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {EIP712Upgradeable} from "../../../utils/cryptography/EIP712Upgradeable.sol";
import {NoncesUpgradeable} from "../../../utils/NoncesUpgradeable.sol";
import {Initializable} from "../../../proxy/utils/Initializable.sol";
/**
* @dev Implementation of the ERC-20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[ERC-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC-20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*/
abstract contract ERC20PermitUpgradeable is Initializable, ERC20Upgradeable, IERC20Permit, EIP712Upgradeable, NoncesUpgradeable {
bytes32 private constant PERMIT_TYPEHASH =
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
/**
* @dev Permit deadline has expired.
*/
error ERC2612ExpiredSignature(uint256 deadline);
/**
* @dev Mismatched signature.
*/
error ERC2612InvalidSigner(address signer, address owner);
/**
* @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`.
*
* It's a good idea to use the same `name` that is defined as the ERC-20 token name.
*/
function __ERC20Permit_init(string memory name) internal onlyInitializing {
__EIP712_init_unchained(name, "1");
}
function __ERC20Permit_init_unchained(string memory) internal onlyInitializing {}
/**
* @inheritdoc IERC20Permit
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
if (block.timestamp > deadline) {
revert ERC2612ExpiredSignature(deadline);
}
bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));
bytes32 hash = _hashTypedDataV4(structHash);
address signer = ECDSA.recover(hash, v, r, s);
if (signer != owner) {
revert ERC2612InvalidSigner(signer, owner);
}
_approve(owner, spender, value);
}
/**
* @inheritdoc IERC20Permit
*/
function nonces(address owner) public view virtual override(IERC20Permit, NoncesUpgradeable) returns (uint256) {
return super.nonces(owner);
}
/**
* @inheritdoc IERC20Permit
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view virtual returns (bytes32) {
return _domainSeparatorV4();
}
}
"
},
"node_modules/@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/Pausable.sol)
pragma solidity ^0.8.20;
import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract PausableUpgradeable is Initializable, ContextUpgradeable {
/// @custom:storage-location erc7201:openzeppelin.storage.Pausable
struct PausableStorage {
bool _paused;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Pausable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant PausableStorageLocation = 0xcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f03300;
function _getPausableStorage() private pure returns (PausableStorage storage $) {
assembly {
$.slot := PausableStorageLocation
}
}
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
/**
* @dev The operation failed because the contract is paused.
*/
error EnforcedPause();
/**
* @dev The operation failed because the contract is not paused.
*/
error ExpectedPause();
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
function __Pausable_init() internal onlyInitializing {
}
function __Pausable_init_unchained() internal onlyInitializing {
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
PausableStorage storage $ = _getPausableStorage();
return $._paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
if (paused()) {
revert EnforcedPause();
}
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
if (!paused()) {
revert ExpectedPause();
}
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
PausableStorage storage $ = _getPausableStorage();
$._paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
PausableStorage storage $ = _getPausableStorage();
$._paused = false;
emit Unpaused(_msgSender());
}
}
"
},
"node_modules/@openzeppelin/contracts/utils/structs/EnumerableSet.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
pragma solidity ^0.8.20;
import {Arrays} from "../Arrays.sol";
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
* - Set can be cleared (all elements removed) in O(n).
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*
* [WARNING]
* ====
* Tr
Submitted on: 2025-11-07 10:46:30
Comments
Log in to comment.
No comments yet.