// SPDX-License-Identifier: BUSL-1.1pragmasolidity 0.8.21;contractError{
errorFluidVaultError(uint256 errorId_);
/// @notice used to simulate liquidation to find the maximum liquidatable amountserrorFluidLiquidateResult(uint256 colLiquidated, uint256 debtLiquidated);
}
Contract Source Code
File 4 of 6: errorTypes.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity 0.8.21;libraryErrorTypes{
/***********************************|
| Vault Factory |
|__________________________________*/uint256internalconstant VaultFactory__InvalidOperation =30001;
uint256internalconstant VaultFactory__Unauthorized =30002;
uint256internalconstant VaultFactory__SameTokenNotAllowed =30003;
uint256internalconstant VaultFactory__InvalidParams =30004;
uint256internalconstant VaultFactory__InvalidVault =30005;
uint256internalconstant VaultFactory__InvalidVaultAddress =30006;
uint256internalconstant VaultFactory__OnlyDelegateCallAllowed =30007;
/***********************************|
| VaultT1 |
|__________________________________*//// @notice thrown at reentrancyuint256internalconstant VaultT1__AlreadyEntered =31001;
/// @notice thrown when user sends deposit & borrow amount as 0uint256internalconstant VaultT1__InvalidOperateAmount =31002;
/// @notice thrown when msg.value is not in sync with native token deposit or paybackuint256internalconstant VaultT1__InvalidMsgValueOperate =31003;
/// @notice thrown when msg.sender is not the owner of the vaultuint256internalconstant VaultT1__NotAnOwner =31004;
/// @notice thrown when user's position does not exist. Sending the wrong index from the frontenduint256internalconstant VaultT1__TickIsEmpty =31005;
/// @notice thrown when the user's position is above CF and the user tries to make it more risky by trying to withdraw or borrowuint256internalconstant VaultT1__PositionAboveCF =31006;
/// @notice thrown when the top tick is not initialized. Happens if the vault is totally new or all the user's leftuint256internalconstant VaultT1__TopTickDoesNotExist =31007;
/// @notice thrown when msg.value in liquidate is not in sync paybackuint256internalconstant VaultT1__InvalidMsgValueLiquidate =31008;
/// @notice thrown when slippage is more on liquidation than what the liquidator sentuint256internalconstant VaultT1__ExcessSlippageLiquidation =31009;
/// @notice thrown when msg.sender is not the rebalancer/reserve contractuint256internalconstant VaultT1__NotRebalancer =31010;
/// @notice thrown when NFT of one vault interacts with the NFT of other vaultuint256internalconstant VaultT1__NftNotOfThisVault =31011;
/// @notice thrown when the token is not initialized on the liquidity contractuint256internalconstant VaultT1__TokenNotInitialized =31012;
/// @notice thrown when admin updates fallback if a non-auth calls vaultuint256internalconstant VaultT1__NotAnAuth =31013;
/// @notice thrown in operate when user tries to witdhraw more collateral than depositeduint256internalconstant VaultT1__ExcessCollateralWithdrawal =31014;
/// @notice thrown in operate when user tries to payback more debt than borroweduint256internalconstant VaultT1__ExcessDebtPayback =31015;
/// @notice thrown when user try to withdrawal more than operate's withdrawal limituint256internalconstant VaultT1__WithdrawMoreThanOperateLimit =31016;
/// @notice thrown when caller of liquidityCallback is not Liquidityuint256internalconstant VaultT1__InvalidLiquidityCallbackAddress =31017;
/// @notice thrown when reentrancy is not already onuint256internalconstant VaultT1__NotEntered =31018;
/// @notice thrown when someone directly calls secondary implementation contractuint256internalconstant VaultT1__OnlyDelegateCallAllowed =31019;
/// @notice thrown when the safeTransferFrom for a token amount faileduint256internalconstant VaultT1__TransferFromFailed =31020;
/// @notice thrown when exchange price overflows while updating on storageuint256internalconstant VaultT1__ExchangePriceOverFlow =31021;
/// @notice thrown when debt to liquidate amt is sent wronguint256internalconstant VaultT1__InvalidLiquidationAmt =31022;
/// @notice thrown when user debt or collateral goes above 2**128 or below -2**128uint256internalconstant VaultT1__UserCollateralDebtExceed =31023;
/// @notice thrown if on liquidation branch debt becomes lower than 100uint256internalconstant VaultT1__BranchDebtTooLow =31024;
/// @notice thrown when tick's debt is less than 10000uint256internalconstant VaultT1__TickDebtTooLow =31025;
/// @notice thrown when the received new liquidity exchange price is of unexpected value (< than the old one)uint256internalconstant VaultT1__LiquidityExchangePriceUnexpected =31026;
/// @notice thrown when user's debt is less than 10000uint256internalconstant VaultT1__UserDebtTooLow =31027;
/// @notice thrown when on only payback and only deposit the ratio of position increasesuint256internalconstant VaultT1__InvalidPaybackOrDeposit =31028;
/// @notice thrown when liquidation just happens of a single partialuint256internalconstant VaultT1__InvalidLiquidation =31029;
/// @notice thrown when msg.value is sent wrong in rebalanceuint256internalconstant VaultT1__InvalidMsgValueInRebalance =31030;
/// @notice thrown when nothing rebalanceduint256internalconstant VaultT1__NothingToRebalance =31031;
/***********************************|
| ERC721 |
|__________________________________*/uint256internalconstant ERC721__InvalidParams =32001;
uint256internalconstant ERC721__Unauthorized =32002;
uint256internalconstant ERC721__InvalidOperation =32003;
uint256internalconstant ERC721__UnsafeRecipient =32004;
uint256internalconstant ERC721__OutOfBoundsIndex =32005;
/***********************************|
| Vault Admin |
|__________________________________*//// @notice thrown when admin tries to setup invalid value which are crossing limitsuint256internalconstant VaultT1Admin__ValueAboveLimit =33001;
/// @notice when someone directly calls admin implementation contractuint256internalconstant VaultT1Admin__OnlyDelegateCallAllowed =33002;
/// @notice thrown when auth sends NFT ID as 0 while collecting dust debtuint256internalconstant VaultT1Admin__NftIdShouldBeNonZero =33003;
/// @notice thrown when trying to collect dust debt of NFT which is not of this vaultuint256internalconstant VaultT1Admin__NftNotOfThisVault =33004;
/// @notice thrown when dust debt of NFT is 0, meaning nothing to collectuint256internalconstant VaultT1Admin__DustDebtIsZero =33005;
/// @notice thrown when final debt after liquidation is not 0, meaning position 100% liquidateduint256internalconstant VaultT1Admin__FinalDebtShouldBeZero =33006;
/// @notice thrown when NFT is not liquidated stateuint256internalconstant VaultT1Admin__NftNotLiquidated =33007;
/// @notice thrown when total absorbed dust debt is 0uint256internalconstant VaultT1Admin__AbsorbedDustDebtIsZero =33008;
/// @notice thrown when address is set as 0uint256internalconstant VaultT1Admin__AddressZeroNotAllowed =33009;
/***********************************|
| Vault Rewards |
|__________________________________*/uint256internalconstant VaultRewards__Unauthorized =34001;
uint256internalconstant VaultRewards__AddressZero =34002;
uint256internalconstant VaultRewards__InvalidParams =34003;
uint256internalconstant VaultRewards__NewMagnifierSameAsOldMagnifier =34004;
uint256internalconstant VaultRewards__NotTheInitiator =34005;
uint256internalconstant VaultRewards__AlreadyStarted =34006;
uint256internalconstant VaultRewards__RewardsNotStartedOrEnded =34007;
}
Contract Source Code
File 5 of 6: main.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity 0.8.21;import { Owned } from"solmate/src/auth/Owned.sol";
import { ERC721 } from"./ERC721/ERC721.sol";
import { ErrorTypes } from"../errorTypes.sol";
import { StorageRead } from"../../../libraries/storageRead.sol";
abstractcontractVaultFactoryVariablesisOwned, ERC721, StorageRead{
/// @dev ERC721 tokens namestringinternalconstant ERC721_NAME ="Fluid Vault";
/// @dev ERC721 tokens symbolstringinternalconstant ERC721_SYMBOL ="fVLT";
/*//////////////////////////////////////////////////////////////
STORAGE VARIABLES
//////////////////////////////////////////////////////////////*/// ------------ storage variables from inherited contracts (Owned and ERC721) come before vars here --------// ----------------------- slot 0 ---------------------------// address public owner; // from Owned// 12 bytes empty// ----------------------- slot 1 ---------------------------// string public name;// ----------------------- slot 2 ---------------------------// string public symbol;// ----------------------- slot 3 ---------------------------// mapping(uint256 => uint256) internal _tokenConfig;// ----------------------- slot 4 ---------------------------// mapping(address => mapping(uint256 => uint256)) internal _ownerConfig;// ----------------------- slot 5 ---------------------------// uint256 public totalSupply;// ----------------------- slot 6 ---------------------------// mapping(uint256 => address) public getApproved;// ----------------------- slot 7 ---------------------------// mapping(address => mapping(address => bool)) public isApprovedForAll;// ----------------------- slot 8 ---------------------------/// @dev deployer can deploy new Vault contract/// owner can add/remove deployer./// Owner is deployer by default.mapping(address=>bool) internal _deployers;
// ----------------------- slot 9 ---------------------------/// @dev global auths can update any vault config./// owner can add/remove global auths./// Owner is global auth by default.mapping(address=>bool) internal _globalAuths;
// ----------------------- slot 10 ---------------------------/// @dev vault auths can update specific vault config./// owner can add/remove vault auths./// Owner is vault auth by default./// vault => auth => add/removemapping(address=>mapping(address=>bool)) internal _vaultAuths;
// ----------------------- slot 11 ---------------------------/// @dev total no of vaults deployed by the factory/// only addresses that have deployer role or owner can deploy new vault.uint256internal _totalVaults;
// ----------------------- slot 12 ---------------------------/// @dev vault deployment logics for deploying vault/// These logic contracts hold the deployment logics of specific vaults and are called via .delegatecall inside deployVault()./// only addresses that have owner can add/remove new vault deployment logic.mapping(address=>bool) internal _vaultDeploymentLogics;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/constructor(address owner_) Owned(owner_) ERC721(ERC721_NAME, ERC721_SYMBOL) {}
}
abstractcontractVaultFactoryEvents{
/// @dev Emitted when a new vault is deployed./// @param vault The address of the newly deployed vault./// @param vaultId The id of the newly deployed vault.eventVaultDeployed(addressindexed vault, uint256indexed vaultId);
/// @dev Emitted when a new token/position is minted by a vault./// @param vault The address of the vault that minted the token./// @param user The address of the user who received the minted token./// @param tokenId The ID of the newly minted token.eventNewPositionMinted(addressindexed vault, addressindexed user, uint256indexed tokenId);
/// @dev Emitted when the deployer is modified by owner./// @param deployer Address whose deployer status is updated./// @param allowed Indicates whether the address is authorized as a deployer or not.eventLogSetDeployer(addressindexed deployer, boolindexed allowed);
/// @dev Emitted when the globalAuth is modified by owner./// @param globalAuth Address whose globalAuth status is updated./// @param allowed Indicates whether the address is authorized as a deployer or not.eventLogSetGlobalAuth(addressindexed globalAuth, boolindexed allowed);
/// @dev Emitted when the vaultAuth is modified by owner./// @param vaultAuth Address whose vaultAuth status is updated./// @param allowed Indicates whether the address is authorized as a deployer or not./// @param vault Address of the specific vault related to the authorization change.eventLogSetVaultAuth(addressindexed vaultAuth, boolindexed allowed, addressindexed vault);
/// @dev Emitted when the vault deployment logic is modified by owner./// @param vaultDeploymentLogic The address of the vault deployment logic contract./// @param allowed Indicates whether the address is authorized as a deployer or not.eventLogSetVaultDeploymentLogic(addressindexed vaultDeploymentLogic, boolindexed allowed);
}
abstractcontractVaultFactoryCoreisVaultFactoryVariables, VaultFactoryEvents{
constructor(address owner_) validAddress(owner_) VaultFactoryVariables(owner_) {}
/// @dev validates that an address is not the zero addressmodifiervalidAddress(address value_) {
if (value_ ==address(0)) {
revert FluidVaultError(ErrorTypes.VaultFactory__InvalidParams);
}
_;
}
}
/// @dev Implements Vault Factory auth-only callable methods. Owner / auths can set various config values and/// can define the allow-listed deployers.abstractcontractVaultFactoryAuthisVaultFactoryCore{
/// @notice Sets an address (`deployer_`) as allowed deployer or not./// This function can only be called by the owner./// @param deployer_ The address to be set as deployer./// @param allowed_ A boolean indicating whether the specified address is allowed to deploy vaults.functionsetDeployer(address deployer_, bool allowed_) externalonlyOwnervalidAddress(deployer_) {
_deployers[deployer_] = allowed_;
emit LogSetDeployer(deployer_, allowed_);
}
/// @notice Sets an address (`globalAuth_`) as a global authorization or not./// This function can only be called by the owner./// @param globalAuth_ The address to be set as global authorization./// @param allowed_ A boolean indicating whether the specified address is allowed to update any vault config.functionsetGlobalAuth(address globalAuth_, bool allowed_) externalonlyOwnervalidAddress(globalAuth_) {
_globalAuths[globalAuth_] = allowed_;
emit LogSetGlobalAuth(globalAuth_, allowed_);
}
/// @notice Sets an address (`vaultAuth_`) as allowed vault authorization or not for a specific vault (`vault_`)./// This function can only be called by the owner./// @param vault_ The address of the vault for which the authorization is being set./// @param vaultAuth_ The address to be set as vault authorization./// @param allowed_ A boolean indicating whether the specified address is allowed to update the specific vault config.functionsetVaultAuth(address vault_,
address vaultAuth_,
bool allowed_
) externalonlyOwnervalidAddress(vaultAuth_) {
_vaultAuths[vault_][vaultAuth_] = allowed_;
emit LogSetVaultAuth(vaultAuth_, allowed_, vault_);
}
/// @notice Sets an address as allowed vault deployment logic (`deploymentLogic_`) contract or not./// This function can only be called by the owner./// @param deploymentLogic_ The address of the vault deployment logic contract to be set./// @param allowed_ A boolean indicating whether the specified address is allowed to deploy new type of vault.functionsetVaultDeploymentLogic(address deploymentLogic_,
bool allowed_
) publiconlyOwnervalidAddress(deploymentLogic_) {
_vaultDeploymentLogics[deploymentLogic_] = allowed_;
emit LogSetVaultDeploymentLogic(deploymentLogic_, allowed_);
}
/// @notice Spell allows owner aka governance to do any arbitrary call on factory/// @param target_ Address to which the call needs to be delegated/// @param data_ Data to execute at the delegated addressfunctionspell(address target_, bytesmemory data_) externalonlyOwnerreturns (bytesmemory response_) {
assembly {
let succeeded :=delegatecall(gas(), target_, add(data_, 0x20), mload(data_), 0, 0)
let size :=returndatasize()
response_ :=mload(0x40)
mstore(0x40, add(response_, and(add(add(size, 0x20), 0x1f), not(0x1f))))
mstore(response_, size)
returndatacopy(add(response_, 0x20), 0, size)
switchiszero(succeeded)
case1 {
// throw if delegatecall failedreturndatacopy(0x00, 0x00, size)
revert(0x00, size)
}
}
}
/// @notice Checks if the provided address (`deployer_`) is authorized as a deployer./// @param deployer_ The address to be checked for deployer authorization./// @return Returns `true` if the address is a deployer, otherwise `false`.functionisDeployer(address deployer_) publicviewreturns (bool) {
return _deployers[deployer_] || owner == deployer_;
}
/// @notice Checks if the provided address (`globalAuth_`) has global vault authorization privileges./// @param globalAuth_ The address to be checked for global authorization privileges./// @return Returns `true` if the given address has global authorization privileges, otherwise `false`.functionisGlobalAuth(address globalAuth_) publicviewreturns (bool) {
return _globalAuths[globalAuth_] || owner == globalAuth_;
}
/// @notice Checks if the provided address (`vaultAuth_`) has vault authorization privileges for the specified vault (`vault_`)./// @param vault_ The address of the vault to check./// @param vaultAuth_ The address to be checked for vault authorization privileges./// @return Returns `true` if the given address has vault authorization privileges for the specified vault, otherwise `false`.functionisVaultAuth(address vault_, address vaultAuth_) publicviewreturns (bool) {
return _vaultAuths[vault_][vaultAuth_] || owner == vaultAuth_;
}
/// @notice Checks if the provided (`vaultDeploymentLogic_`) address has authorization for vault deployment./// @param vaultDeploymentLogic_ The address of the vault deploy logic to check for authorization privileges./// @return Returns `true` if the given address has authorization privileges for vault deployment, otherwise `false`.functionisVaultDeploymentLogic(address vaultDeploymentLogic_) publicviewreturns (bool) {
return _vaultDeploymentLogics[vaultDeploymentLogic_];
}
}
/// @dev implements VaultFactory deploy vault related methods.abstractcontractVaultFactoryDeploymentisVaultFactoryCore, VaultFactoryAuth{
/// @dev Deploys a contract using the CREATE opcode with the provided bytecode (`bytecode_`)./// This is an internal function, meant to be used within the contract to facilitate the deployment of other contracts./// @param bytecode_ The bytecode of the contract to be deployed./// @return address_ Returns the address of the deployed contract.function_deploy(bytesmemory bytecode_) internalreturns (address address_) {
if (bytecode_.length==0) {
revert FluidVaultError(ErrorTypes.VaultFactory__InvalidOperation);
}
/// @solidity memory-safe-assemblyassembly {
address_ :=create(0, add(bytecode_, 0x20), mload(bytecode_))
}
if (address_ ==address(0)) {
revert FluidVaultError(ErrorTypes.VaultFactory__InvalidOperation);
}
}
/// @notice Deploys a new vault using the specified deployment logic `vaultDeploymentLogic_` and data `vaultDeploymentData_`./// Only accounts with deployer access or the owner can deploy a new vault./// @param vaultDeploymentLogic_ The address of the vault deployment logic contract./// @param vaultDeploymentData_ The data to be used for vault deployment./// @return vault_ Returns the address of the newly deployed vault.functiondeployVault(address vaultDeploymentLogic_,
bytescalldata vaultDeploymentData_
) externalreturns (address vault_) {
// Revert if msg.sender doesn't have deployer access or is an owner.if (!isDeployer(msg.sender)) revert FluidVaultError(ErrorTypes.VaultFactory__Unauthorized);
// Revert if vaultDeploymentLogic_ is not whitelisted.if (!isVaultDeploymentLogic(vaultDeploymentLogic_))
revert FluidVaultError(ErrorTypes.VaultFactory__Unauthorized);
// Vault ID for the new vault and also acts as `nonce` for CREATEuint256 vaultId_ =++_totalVaults;
// compute vault address for vault id.
vault_ = getVaultAddress(vaultId_);
// deploy the vault using vault deployment logic by making .delegatecall
(bool success_, bytesmemory data_) = vaultDeploymentLogic_.delegatecall(vaultDeploymentData_);
if (!(success_ && vault_ == _deploy(abi.decode(data_, (bytes))) && isVault(vault_))) {
revert FluidVaultError(ErrorTypes.VaultFactory__InvalidVaultAddress);
}
emit VaultDeployed(vault_, vaultId_);
}
/// @notice Computes the address of a vault based on its given ID (`vaultId_`)./// @param vaultId_ The ID of the vault./// @return vault_ Returns the computed address of the vault.functiongetVaultAddress(uint256 vaultId_) publicviewreturns (address vault_) {
// @dev based on https://ethereum.stackexchange.com/a/61413// nonce of smart contract always starts with 1. so, with nonce 0 there won't be any deployment// hence, nonce of vault deployment starts with 1.bytesmemory data;
if (vaultId_ ==0x00) {
returnaddress(0);
} elseif (vaultId_ <=0x7f) {
data =abi.encodePacked(bytes1(0xd6), bytes1(0x94), address(this), uint8(vaultId_));
} elseif (vaultId_ <=0xff) {
data =abi.encodePacked(bytes1(0xd7), bytes1(0x94), address(this), bytes1(0x81), uint8(vaultId_));
} elseif (vaultId_ <=0xffff) {
data =abi.encodePacked(bytes1(0xd8), bytes1(0x94), address(this), bytes1(0x82), uint16(vaultId_));
} elseif (vaultId_ <=0xffffff) {
data =abi.encodePacked(bytes1(0xd9), bytes1(0x94), address(this), bytes1(0x83), uint24(vaultId_));
} else {
data =abi.encodePacked(bytes1(0xda), bytes1(0x94), address(this), bytes1(0x84), uint32(vaultId_));
}
returnaddress(uint160(uint256(keccak256(data))));
}
/// @notice Checks if a given address (`vault_`) corresponds to a valid vault./// @param vault_ The vault address to check./// @return Returns `true` if the given address corresponds to a valid vault, otherwise `false`.functionisVault(address vault_) publicviewreturns (bool) {
if (vault_.code.length==0) {
returnfalse;
} else {
// VAULT_ID() function signature is 0x540acabc
(bool success_, bytesmemory data_) = vault_.staticcall(hex"540acabc");
return success_ && vault_ == getVaultAddress(abi.decode(data_, (uint256)));
}
}
/// @notice Returns the total number of vaults deployed by the factory./// @return Returns the total number of vaults.functiontotalVaults() externalviewreturns (uint256) {
return _totalVaults;
}
}
abstractcontractVaultFactoryERC721isVaultFactoryCore, VaultFactoryDeployment{
/// @notice Mints a new ERC721 token for a specific vault (`vaultId_`) to a specified user (`user_`)./// Only the corresponding vault is authorized to mint a token./// @param vaultId_ The ID of the vault that's minting the token./// @param user_ The address receiving the minted token./// @return tokenId_ The ID of the newly minted token.functionmint(uint256 vaultId_, address user_) externalreturns (uint256 tokenId_) {
if (msg.sender!= getVaultAddress(vaultId_)) revert FluidVaultError(ErrorTypes.VaultFactory__InvalidVault);
// Using _mint() instead of _safeMint() to allow any msg.sender to receive ERC721 without onERC721Received holder.
tokenId_ = _mint(user_, vaultId_);
emit NewPositionMinted(msg.sender, user_, tokenId_);
}
/// @notice Returns the URI of the specified token ID (`id_`)./// In this implementation, an empty string is returned as no specific URI is defined./// @param id_ The ID of the token to query./// @return An empty string since no specific URI is defined in this implementation.functiontokenURI(uint256 id_) publicviewvirtualoverridereturns (stringmemory) {
return"";
}
}
/// @title Fluid VaultFactory/// @notice creates Fluid vault protocol vaults, which are interacting with Fluid Liquidity to deposit / borrow funds./// Vaults are created at a deterministic address, given an incrementing `vaultId` (see `getVaultAddress()`)./// Vaults can only be deployed by allow-listed deployer addresses./// This factory also implements ERC721-Enumerable, the NFTs are used to represent created user positions. Only vaults/// can mint new NFTs./// @dev Note the deployed vaults start out with no config at Liquidity contract./// This must be done by Liquidity auths in a separate step, otherwise no deposits will be possible./// This contract is not upgradeable. It supports adding new vault deployment logic contracts for new, future vaults.contractFluidVaultFactoryisVaultFactoryCore, VaultFactoryAuth, VaultFactoryDeployment, VaultFactoryERC721{
constructor(address owner_) VaultFactoryCore(owner_) {}
}
Contract Source Code
File 6 of 6: storageRead.sol
// SPDX-License-Identifier: BUSL-1.1pragmasolidity 0.8.21;/// @notice implements a method to read uint256 data from storage at a bytes32 storage slot key.contractStorageRead{
functionreadFromStorage(bytes32 slot_) publicviewreturns (uint256 result_) {
assembly {
result_ :=sload(slot_) // read value from the storage slot
}
}
}