-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #43 from symbioticfi/tokenized-vault
Tokenize vault
- Loading branch information
Showing
4 changed files
with
3,283 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity 0.8.25; | ||
|
||
import {Vault} from "./Vault.sol"; | ||
|
||
import {IVaultTokenized} from "../../interfaces/vault/IVaultTokenized.sol"; | ||
import {IVault} from "../../interfaces/vault/IVault.sol"; | ||
|
||
import {Checkpoints} from "../libraries/Checkpoints.sol"; | ||
import {ERC4626Math} from "../libraries/ERC4626Math.sol"; | ||
|
||
import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; | ||
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; | ||
import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; | ||
import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; | ||
|
||
contract VaultTokenized is Vault, ERC20Upgradeable, IVaultTokenized { | ||
using Checkpoints for Checkpoints.Trace256; | ||
using SafeERC20 for IERC20; | ||
|
||
constructor( | ||
address delegatorFactory, | ||
address slasherFactory, | ||
address vaultFactory | ||
) Vault(delegatorFactory, slasherFactory, vaultFactory) {} | ||
|
||
/** | ||
* @inheritdoc ERC20Upgradeable | ||
*/ | ||
function decimals() public view override returns (uint8) { | ||
return IERC20Metadata(collateral).decimals(); | ||
} | ||
|
||
/** | ||
* @inheritdoc ERC20Upgradeable | ||
*/ | ||
function totalSupply() public view override returns (uint256) { | ||
return activeShares(); | ||
} | ||
|
||
/** | ||
* @inheritdoc ERC20Upgradeable | ||
*/ | ||
function balanceOf( | ||
address account | ||
) public view override returns (uint256) { | ||
return activeSharesOf(account); | ||
} | ||
|
||
/** | ||
* @inheritdoc IVault | ||
*/ | ||
function deposit( | ||
address onBehalfOf, | ||
uint256 amount | ||
) external override(Vault, IVault) nonReentrant returns (uint256 depositedAmount, uint256 mintedShares) { | ||
if (onBehalfOf == address(0)) { | ||
revert InvalidOnBehalfOf(); | ||
} | ||
|
||
if (depositWhitelist && !isDepositorWhitelisted[msg.sender]) { | ||
revert NotWhitelistedDepositor(); | ||
} | ||
|
||
uint256 balanceBefore = IERC20(collateral).balanceOf(address(this)); | ||
IERC20(collateral).safeTransferFrom(msg.sender, address(this), amount); | ||
depositedAmount = IERC20(collateral).balanceOf(address(this)) - balanceBefore; | ||
|
||
if (depositedAmount == 0) { | ||
revert InsufficientDeposit(); | ||
} | ||
|
||
if (isDepositLimit && totalStake() + depositedAmount > depositLimit) { | ||
revert DepositLimitReached(); | ||
} | ||
|
||
uint256 activeStake_ = activeStake(); | ||
uint256 activeShares_ = activeShares(); | ||
|
||
mintedShares = ERC4626Math.previewDeposit(depositedAmount, activeShares_, activeStake_); | ||
|
||
_activeStake.push(Time.timestamp(), activeStake_ + depositedAmount); | ||
_mint(onBehalfOf, mintedShares); | ||
|
||
emit Deposit(msg.sender, onBehalfOf, depositedAmount, mintedShares); | ||
} | ||
|
||
function _withdraw( | ||
address claimer, | ||
uint256 withdrawnAssets, | ||
uint256 burnedShares | ||
) internal override returns (uint256 mintedShares) { | ||
_burn(msg.sender, burnedShares); | ||
_activeStake.push(Time.timestamp(), activeStake() - withdrawnAssets); | ||
|
||
uint256 epoch = currentEpoch() + 1; | ||
uint256 withdrawals_ = withdrawals[epoch]; | ||
uint256 withdrawalsShares_ = withdrawalShares[epoch]; | ||
|
||
mintedShares = ERC4626Math.previewDeposit(withdrawnAssets, withdrawalsShares_, withdrawals_); | ||
|
||
withdrawals[epoch] = withdrawals_ + withdrawnAssets; | ||
withdrawalShares[epoch] = withdrawalsShares_ + mintedShares; | ||
withdrawalSharesOf[epoch][claimer] += mintedShares; | ||
} | ||
|
||
/** | ||
* @inheritdoc ERC20Upgradeable | ||
*/ | ||
function _update(address from, address to, uint256 value) internal override { | ||
if (from == address(0)) { | ||
// Overflow check required: The rest of the code assumes that totalSupply never overflows | ||
_activeShares.push(Time.timestamp(), totalSupply() + value); | ||
} else { | ||
uint256 fromBalance = balanceOf(from); | ||
if (fromBalance < value) { | ||
revert ERC20InsufficientBalance(from, fromBalance, value); | ||
} | ||
unchecked { | ||
// Overflow not possible: value <= fromBalance <= totalSupply. | ||
_activeSharesOf[from].push(Time.timestamp(), fromBalance - value); | ||
} | ||
} | ||
|
||
if (to == address(0)) { | ||
unchecked { | ||
// Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply. | ||
_activeShares.push(Time.timestamp(), totalSupply() - value); | ||
} | ||
} else { | ||
unchecked { | ||
// Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256. | ||
_activeSharesOf[to].push(Time.timestamp(), balanceOf(to) + value); | ||
} | ||
} | ||
|
||
emit Transfer(from, to, value); | ||
} | ||
|
||
function _initialize(uint64 initialVersion, address owner_, bytes calldata data) internal override { | ||
(InitParamsTokenized memory params) = abi.decode(data, (InitParamsTokenized)); | ||
|
||
__ERC20_init(params.name, params.symbol); | ||
|
||
if (params.collateral == address(0)) { | ||
revert InvalidCollateral(); | ||
} | ||
|
||
if (params.epochDuration == 0) { | ||
revert InvalidEpochDuration(); | ||
} | ||
|
||
if (params.defaultAdminRoleHolder == address(0)) { | ||
if (params.depositWhitelistSetRoleHolder == address(0)) { | ||
if (params.depositWhitelist) { | ||
if (params.depositorWhitelistRoleHolder == address(0)) { | ||
revert MissingRoles(); | ||
} | ||
} else if (params.depositorWhitelistRoleHolder != address(0)) { | ||
revert MissingRoles(); | ||
} | ||
} | ||
|
||
if (params.isDepositLimitSetRoleHolder == address(0)) { | ||
if (params.isDepositLimit) { | ||
if (params.depositLimit == 0 && params.depositLimitSetRoleHolder == address(0)) { | ||
revert MissingRoles(); | ||
} | ||
} else if (params.depositLimit != 0 || params.depositLimitSetRoleHolder != address(0)) { | ||
revert MissingRoles(); | ||
} | ||
} | ||
} | ||
|
||
collateral = params.collateral; | ||
|
||
burner = params.burner; | ||
|
||
epochDurationInit = Time.timestamp(); | ||
epochDuration = params.epochDuration; | ||
|
||
depositWhitelist = params.depositWhitelist; | ||
|
||
isDepositLimit = params.isDepositLimit; | ||
depositLimit = params.depositLimit; | ||
|
||
if (params.defaultAdminRoleHolder != address(0)) { | ||
_grantRole(DEFAULT_ADMIN_ROLE, params.defaultAdminRoleHolder); | ||
} | ||
if (params.depositWhitelistSetRoleHolder != address(0)) { | ||
_grantRole(DEPOSIT_WHITELIST_SET_ROLE, params.depositWhitelistSetRoleHolder); | ||
} | ||
if (params.depositorWhitelistRoleHolder != address(0)) { | ||
_grantRole(DEPOSITOR_WHITELIST_ROLE, params.depositorWhitelistRoleHolder); | ||
} | ||
if (params.isDepositLimitSetRoleHolder != address(0)) { | ||
_grantRole(IS_DEPOSIT_LIMIT_SET_ROLE, params.isDepositLimitSetRoleHolder); | ||
} | ||
if (params.depositLimitSetRoleHolder != address(0)) { | ||
_grantRole(DEPOSIT_LIMIT_SET_ROLE, params.depositLimitSetRoleHolder); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
import {IVault} from "./IVault.sol"; | ||
|
||
interface IVaultTokenized is IVault { | ||
/** | ||
* @notice Initial parameters needed for a tokenized vault deployment. | ||
* @param collateral vault's underlying collateral | ||
* @param burner vault's burner to issue debt to (e.g., 0xdEaD or some unwrapper contract) | ||
* @param epochDuration duration of the vault epoch (it determines sync points for withdrawals) | ||
* @param depositWhitelist if enabling deposit whitelist | ||
* @param isDepositLimit if enabling deposit limit | ||
* @param depositLimit deposit limit (maximum amount of the collateral that can be in the vault simultaneously) | ||
* @param defaultAdminRoleHolder address of the initial DEFAULT_ADMIN_ROLE holder | ||
* @param depositWhitelistSetRoleHolder address of the initial DEPOSIT_WHITELIST_SET_ROLE holder | ||
* @param depositorWhitelistRoleHolder address of the initial DEPOSITOR_WHITELIST_ROLE holder | ||
* @param isDepositLimitSetRoleHolder address of the initial IS_DEPOSIT_LIMIT_SET_ROLE holder | ||
* @param depositLimitSetRoleHolder address of the initial DEPOSIT_LIMIT_SET_ROLE holder | ||
* @param name name for the ERC20 tokenized vault | ||
* @param symbol symbol for the ERC20 tokenized vault | ||
*/ | ||
struct InitParamsTokenized { | ||
address collateral; | ||
address burner; | ||
uint48 epochDuration; | ||
bool depositWhitelist; | ||
bool isDepositLimit; | ||
uint256 depositLimit; | ||
address defaultAdminRoleHolder; | ||
address depositWhitelistSetRoleHolder; | ||
address depositorWhitelistRoleHolder; | ||
address isDepositLimitSetRoleHolder; | ||
address depositLimitSetRoleHolder; | ||
string name; | ||
string symbol; | ||
} | ||
} |
Oops, something went wrong.