mirror of
https://github.com/Instadapp/fluid-contracts-public.git
synced 2024-07-29 21:57:37 +00:00
d7a58e88ff
ARB: deploy protocols
365 lines
19 KiB
Solidity
365 lines
19 KiB
Solidity
// SPDX-License-Identifier: BUSL-1.1
|
|
pragma solidity 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";
|
|
|
|
abstract contract VaultFactoryVariables is Owned, ERC721, StorageRead {
|
|
/// @dev ERC721 tokens name
|
|
string internal constant ERC721_NAME = "Fluid Vault";
|
|
/// @dev ERC721 tokens symbol
|
|
string internal constant 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/remove
|
|
mapping(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.
|
|
uint256 internal _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) {}
|
|
}
|
|
|
|
abstract contract VaultFactoryEvents {
|
|
/// @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.
|
|
event VaultDeployed(address indexed vault, uint256 indexed 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.
|
|
event NewPositionMinted(address indexed vault, address indexed user, uint256 indexed 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.
|
|
event LogSetDeployer(address indexed deployer, bool indexed 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.
|
|
event LogSetGlobalAuth(address indexed globalAuth, bool indexed 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.
|
|
event LogSetVaultAuth(address indexed vaultAuth, bool indexed allowed, address indexed 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.
|
|
event LogSetVaultDeploymentLogic(address indexed vaultDeploymentLogic, bool indexed allowed);
|
|
}
|
|
|
|
abstract contract VaultFactoryCore is VaultFactoryVariables, VaultFactoryEvents {
|
|
constructor(address owner_) validAddress(owner_) VaultFactoryVariables(owner_) {}
|
|
|
|
/// @dev validates that an address is not the zero address
|
|
modifier validAddress(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.
|
|
abstract contract VaultFactoryAuth is VaultFactoryCore {
|
|
/// @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.
|
|
function setDeployer(address deployer_, bool allowed_) external onlyOwner validAddress(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.
|
|
function setGlobalAuth(address globalAuth_, bool allowed_) external onlyOwner validAddress(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.
|
|
function setVaultAuth(
|
|
address vault_,
|
|
address vaultAuth_,
|
|
bool allowed_
|
|
) external onlyOwner validAddress(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.
|
|
function setVaultDeploymentLogic(
|
|
address deploymentLogic_,
|
|
bool allowed_
|
|
) public onlyOwner validAddress(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 address
|
|
function spell(address target_, bytes memory data_) external onlyOwner returns (bytes memory 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)
|
|
|
|
switch iszero(succeeded)
|
|
case 1 {
|
|
// throw if delegatecall failed
|
|
returndatacopy(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`.
|
|
function isDeployer(address deployer_) public view returns (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`.
|
|
function isGlobalAuth(address globalAuth_) public view returns (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`.
|
|
function isVaultAuth(address vault_, address vaultAuth_) public view returns (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`.
|
|
function isVaultDeploymentLogic(address vaultDeploymentLogic_) public view returns (bool) {
|
|
return _vaultDeploymentLogics[vaultDeploymentLogic_];
|
|
}
|
|
}
|
|
|
|
/// @dev implements VaultFactory deploy vault related methods.
|
|
abstract contract VaultFactoryDeployment is VaultFactoryCore, 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(bytes memory bytecode_) internal returns (address address_) {
|
|
if (bytecode_.length == 0) {
|
|
revert FluidVaultError(ErrorTypes.VaultFactory__InvalidOperation);
|
|
}
|
|
/// @solidity memory-safe-assembly
|
|
assembly {
|
|
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.
|
|
function deployVault(
|
|
address vaultDeploymentLogic_,
|
|
bytes calldata vaultDeploymentData_
|
|
) external returns (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 CREATE
|
|
uint256 vaultId_ = ++_totalVaults;
|
|
|
|
// compute vault address for vault id.
|
|
vault_ = getVaultAddress(vaultId_);
|
|
|
|
// deploy the vault using vault deployment logic by making .delegatecall
|
|
(bool success_, bytes memory 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.
|
|
function getVaultAddress(uint256 vaultId_) public view returns (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.
|
|
bytes memory data;
|
|
if (vaultId_ == 0x00) {
|
|
return address(0);
|
|
} else if (vaultId_ <= 0x7f) {
|
|
data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), address(this), uint8(vaultId_));
|
|
} else if (vaultId_ <= 0xff) {
|
|
data = abi.encodePacked(bytes1(0xd7), bytes1(0x94), address(this), bytes1(0x81), uint8(vaultId_));
|
|
} else if (vaultId_ <= 0xffff) {
|
|
data = abi.encodePacked(bytes1(0xd8), bytes1(0x94), address(this), bytes1(0x82), uint16(vaultId_));
|
|
} else if (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_));
|
|
}
|
|
|
|
return address(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`.
|
|
function isVault(address vault_) public view returns (bool) {
|
|
if (vault_.code.length == 0) {
|
|
return false;
|
|
} else {
|
|
// VAULT_ID() function signature is 0x540acabc
|
|
(bool success_, bytes memory 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.
|
|
function totalVaults() external view returns (uint256) {
|
|
return _totalVaults;
|
|
}
|
|
}
|
|
|
|
abstract contract VaultFactoryERC721 is VaultFactoryCore, 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.
|
|
function mint(uint256 vaultId_, address user_) external returns (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.
|
|
function tokenURI(uint256 id_) public view virtual override returns (string memory) {
|
|
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.
|
|
contract FluidVaultFactory is VaultFactoryCore, VaultFactoryAuth, VaultFactoryDeployment, VaultFactoryERC721 {
|
|
constructor(address owner_) VaultFactoryCore(owner_) {}
|
|
}
|