Skip to content

Commit

Permalink
Merge pull request #43 from symbioticfi/tokenized-vault
Browse files Browse the repository at this point in the history
Tokenize vault
  • Loading branch information
1kresh authored Sep 26, 2024
2 parents de5f50e + edfc1b6 commit dc13f19
Show file tree
Hide file tree
Showing 4 changed files with 3,283 additions and 4 deletions.
8 changes: 4 additions & 4 deletions src/contracts/vault/Vault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau
function deposit(
address onBehalfOf,
uint256 amount
) external nonReentrant returns (uint256 depositedAmount, uint256 mintedShares) {
) external virtual nonReentrant returns (uint256 depositedAmount, uint256 mintedShares) {
if (onBehalfOf == address(0)) {
revert InvalidOnBehalfOf();
}
Expand Down Expand Up @@ -391,7 +391,7 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau
address claimer,
uint256 withdrawnAssets,
uint256 burnedShares
) private returns (uint256 mintedShares) {
) internal virtual returns (uint256 mintedShares) {
_activeSharesOf[msg.sender].push(Time.timestamp(), activeSharesOf(msg.sender) - burnedShares);
_activeShares.push(Time.timestamp(), activeShares() - burnedShares);
_activeStake.push(Time.timestamp(), activeStake() - withdrawnAssets);
Expand All @@ -409,7 +409,7 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau

function _claim(
uint256 epoch
) private returns (uint256 amount) {
) internal returns (uint256 amount) {
if (epoch >= currentEpoch()) {
revert InvalidEpoch();
}
Expand All @@ -427,7 +427,7 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau
isWithdrawalsClaimed[epoch][msg.sender] = true;
}

function _initialize(uint64, address, bytes calldata data) internal override {
function _initialize(uint64, address, bytes calldata data) internal virtual override {
(IVault.InitParams memory params) = abi.decode(data, (IVault.InitParams));

if (params.collateral == address(0)) {
Expand Down
203 changes: 203 additions & 0 deletions src/contracts/vault/VaultTokenized.sol
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);
}
}
}
38 changes: 38 additions & 0 deletions src/interfaces/vault/IVaultTokenized.sol
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;
}
}
Loading

0 comments on commit dc13f19

Please sign in to comment.