Tutoriel·10 min de lecture·Par Solingo

Building a Minimal ERC-4626 Vault from Scratch

Step-by-step guide to building a tokenized vault following ERC-4626 — the DeFi building block.

# Building a Minimal ERC-4626 Vault from Scratch

ERC-4626 standardizes tokenized vaults. Users deposit assets, receive shares representing proportional ownership.

Core Concept

Deposit: 100 USDC -> 100 shares (1:1)

Yield accrues: vault has 110 USDC for 100 shares

Withdraw: 100 shares -> 110 USDC

Implementation

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract SimpleVault is ERC20 {

using SafeERC20 for IERC20;

IERC20 public immutable asset;

constructor(IERC20 _asset) ERC20("Vault Shares", "vSHARE") {

asset = _asset;

}

function totalAssets() public view returns (uint256) {

return asset.balanceOf(address(this));

}

function convertToShares(uint256 assets) public view returns (uint256) {

uint256 supply = totalSupply();

return supply == 0 ? assets : (assets * supply) / totalAssets();

}

function convertToAssets(uint256 shares) public view returns (uint256) {

uint256 supply = totalSupply();

return supply == 0 ? shares : (shares * totalAssets()) / supply;

}

function deposit(uint256 assets, address receiver) external returns (uint256 shares) {

shares = convertToShares(assets);

require(shares > 0, "Zero shares");

asset.safeTransferFrom(msg.sender, address(this), assets);

_mint(receiver, shares);

}

function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets) {

assets = convertToAssets(shares);

if (msg.sender != owner) _spendAllowance(owner, msg.sender, shares);

_burn(owner, shares);

asset.safeTransfer(receiver, assets);

}

}

Security: Inflation Attack

The first depositor can manipulate share price. Mitigation:

constructor(IERC20 _asset) ERC20("Vault", "vTKN") {

asset = _asset;

_mint(address(1), 1000); // dead shares

}

Key Rules

  • Round DOWN on deposit (fewer shares for user)
  • Round UP on withdraw (more shares burned)
  • Use ReentrancyGuard on deposit/withdraw
  • Test with edge cases: first deposit, zero amounts, max uint
  • Prêt à mettre en pratique ?

    Applique ces concepts avec des exercices interactifs sur Solingo.

    Commencer gratuitement