In April 2022, Yearn Finance, a popular DEFI protocol, announced its support for the newly introduced Ethereum Request for Comment (ERC)-4626 token standard, stating that “Yearn V3 + ERC-4626 = inevitable.”
yearn on Twitter: “5/ Yearn V3 + ERC-4626 = inevitableContributors are already working hard implementing the standard for Yearn’s V3 vaultsSo are devs at @AlchemixFi, @balancer, @RariCapital, @feiprotocol, @OpenZeppelin and elsewherePerhaps one day, we’ll even see a Erc4626 tab on @etherscan / Twitter”
5/ Yearn V3 + ERC-4626 = inevitableContributors are already working hard implementing the standard for Yearn’s V3 vaultsSo are devs at @AlchemixFi, @balancer, @RariCapital, @feiprotocol, @OpenZeppelin and elsewherePerhaps one day, we’ll even see a Erc4626 tab on @etherscan
For Ethereum-based applications, ERCs (Ethereum Request for Comments) are standards that include name registries, package formats, libraries, and tokens requirements. To enable other developers to predict how a set of functions are implemented even without access to the source code of apps that adhere to these standards, this set of standards describes how those functions will be implemented or interacted with in applications and smart contracts.
Given the size of the Ethereum community and the diversity of developer skill levels and implementation methods, ERCs are essential to the Ethereum ecosystem. These guidelines serve as a framework to guarantee that applications and smart contracts are constructed in a consistent manner so that other people can contribute to them.
Before ERC-4626, about 25 ERC standards have been successfully deployed, including ERC-20 (fungible tokens), ERC-721 (non-fungible tokens, or NFTs) and ERC-1155 (the single smart contract multi-token).
There has not been a simpler way to implement yield vaults than ERC-4626. By providing a single API that allows minting tokens from multiple chains on a shared interface, ERC-4626 made varying protocols interoperable.
This article will explain what ERC-4626 is, how yield-bearing vaults work, and how to create a yield-bearing vault ERC-4626 token smart contract.
Smart contracts are created to ensure that agreements between various blockchain transactions are fulfilled. A yield-bearing vault is a smart contract that allows users to deposit different ERC-20 tokens to a pool of tokens in exchange for vTokens (vault tokens).
Users redeem vTokens for initial capital, plus any profits made. The deposited tokens are commutated (pooled) with other comparable crypto assets and distributed over many protocols with the ability to yield profit in the shortest amount of time.
A yield-bearing vault ranks different protocols according to which are the most profitable, and then distributes a fraction of the token pool to the best yielding protocols while keeping a reserve of tokens for users who want to withdraw their funds. The vesting procedure repeats after the harvesting profits are made. Then, the vault converts them back to the initially deposited tokens.
The following diagram depicts how yield-bearing vaults work:
The diagram above may be broken down into a process as follows:
To begin, vault participants must deposit tokens. The vault groups similar ERC tokens into a pool. Participants in the vault are allocated vault tokens, which reflect their claim to the tokens in the pool.
To optimize yield, the vault utilizes a preprogrammed strategy. This strategy looks for the opportunity with the highest yield, and reallocates a percentage of the tokens to optimize pool profits while keeping some in reserve.
When users withdraw their tokens, they are first taken from the vault reserve and then from the yield pool. A withdrawal charge is calculated as the sum of gas fees, strategy fees, and treasury fees when participants withdraw.
You might be wondering how to use yield-bearing vaults now that you know how they work. The following are some examples of yield-bearing vault applications:
The “tokenized vault standard,” also known as ERC-4626, is a standard protocol for tokenized vaults that represents shares of yield-bearing tokens and builds on the ERC-20 token standard.
In other words, ERC-4626 is an extension of ERC-20 that adds new functionality to allow users to profit from their stakes. Previously, using ERC-20 standards, users could only withdraw less than or equal to the amount of tokens they deposited in their account. ERC-4626 allows users to withdraw more than their initial payment over time, based on the amount of profits the vault has generated.
As an extension of ERC-20, ERC-4626 implements the following:
On December 22, 2021, Joey Santoro, the creator of Fei Finance, and five other writers proposed ERC-4626 as an Ethereum Improvement Proposal (EIP) to address the absence of standardization for yield-bearing tokens, which is a problem in decentralized finance.
If you’ve ever wondered how the number “4626” came to be, T11s, a coauthor of the ERC-4626, revealed in a tweet that Joey had suggested the numbers to him during a workout and that the name sounded better than the original proposal of “4700.”
t11s on Twitter: “random 4626 fun-fact:originally we wanted to wait for a clean EIP number like 4700but joey randomly mentioned the current number was 4626 on a call and we both realized it had a sick rhyme to it was in the middle of a run but ran back home to make sure we got it haha / Twitter”
random 4626 fun-fact:originally we wanted to wait for a clean EIP number like 4700but joey randomly mentioned the current number was 4626 on a call and we both realized it had a sick rhyme to it was in the middle of a run but ran back home to make sure we got it haha
After establishing your license identifier and version, the first step in building our vault is to create your ERC20 and ERC4626 interface, also known as IERC20 and IERC4626. Keep in mind that your contract must use every function in your interface; Solidity’s interface has a defined function that is implemented together with the logic. They are inherited, and the original contract makes use of their functions.
Below is an IERC20 interface:
// SPDX-License-Identifier: MIT pragma solidity 0.8.7; interface IERC20{ function transferFrom(address A, address B, uint C) external view returns(bool); function approve() external view returns(uint256); function decimals() external view returns(uint256); function totalSupply() external view returns(uint256); function balanceOf(address account) external view returns(uint256); function transfer(address D, uint amount) external ; }
Below is an IERC4626 interface:
//SPDX-License-Identifier: MIT pragma solidity 0.8.7; interface IERC4626 { function totalAssets() external view returns (uint256); }
After creating your interface, import them into your contract with the ERC20 token standard.
The next step is to write your contract. Give your contract a name, and import the necessary files and libraries.
//SPDX-License-Identifier: MIT pragma solidity 0.8.7; // Import the necessary files and lib import "./Interfaces/IERC4626.sol"; import "./Interfaces/IERC20.sol"; import "https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC20.sol"; // create your contract and inherit the your imports contract TokenizedVault is IERC4626, ERC20 { }
Then, create events. Here, there will be two events: a deposit event for users to deposit tokens, and a withdrawal event for users to request withdrawals:
// create an event that will the withdraw and deposit function event Deposit(address caller, uint256 amt); event Withdraw(address caller, address receiver, uint256 amt, uint256 shares);
The ERC-20 token you imported will be an immutable asset to be used as a constructor. To do this, you will create an immutable variable called asset
that references your ERC20.
After that, make a mapping to demonstrate the user’s status as a shareholder following the deposits:
// create your variables and immutables ERC20 public immutable asset; // a mapping that checks if a user has deposited mapping(address => uint256) shareHolder;
Next, create a constructor that assigns the asset variable to the underlying token whose address associates with the address, name, and symbols of your ERC20 token:
constructor(ERC20 _underlying, string memory _name, string memory _symbol ) ERC20(_name, _symbol, 18) { asset = _underlying; }
Now, write the function for your vault after configuring the contract’s assets and events. A user should be able to deposit tokens for the contract to function as a vault; the user receives proof of deposit after deposit, referred to as “shares”. The user will use this to claim ownership of her shares during withdrawal:
// a deposit function that receives assets from users function deposit(uint256 assets) public{ // checks that the deposit is higher than 0 require (assets > 0, "Deposit less than Zero"); asset.transferFrom(msg.sender, address(this), assets); // checks the value of assets the holder has shareHolder[msg.sender] += assets; // mints the reciept(shares) _mint(msg.sender, assets); emit Deposit(msg.sender, assets); }
Before the function transfers or accepts the token into the vault, perform a check to ensure the user is depositing an actual token (and not zero tokens). The user becomes a shareholder, and each shareholder owns shares equal to the amount they deposited in vault tokens; so, if they deposited 50 ETH, they now own shares equal to 50 iETH.
The following function gives the total amount of assets deposited in this vault:
// returns total number of assets function totalAssets() public view override returns(uint256) { return asset.balanceOf(address(this)); }
Implementing a redeem
function will come next. This internal
function will check whether a user has shares, and if the number of shares is more than zero. If the user is a shareholder, he may redeem any number of shares from the holdings assigned to him.
Before executing the burn functions that convert the shares into the same asset tokens that were initially deposited, we assign a fixed ten percent interest on the number of shares the user owns.
Note that the contract owner may adjust the interest rate on shares as desired.
Finally, it emits the withdrawal event to grant the user access to the assets:
// users to return shares and get thier token back before they can withdraw, and requiers that the user has a deposit function redeem(uint256 shares, address receiver ) internal returns (uint256 assets) { require(shareHolder[msg.sender] > 0, "Not a share holder"); shareHolder[msg.sender] -= shares; uint256 per = (10 * shares) / 100; _burn(msg.sender, shares); assets = shares + per; emit Withdraw(receiver, assets, per); return assets; }
The withdraw
feature is last. A vault that permits deposits but not withdrawals is considered malicious.
To withdraw, the redeem
function is invoked by the withdraw
function, which converts shares into asset tokens and calculates interest on the asset. Then, the user can withdraw her assets and interest using the following:
// allow msg.sender to withdraw his deposit plus interest function withdraw(uint256 shares, address receiver) public { uint256 payout = redeem(shares, receiver); asset.transfer(receiver, payout); } }
The whole contract code can be found in this GitHub gist.
After reading this article, you should hopefully have a better understanding of what an ERC, ERC-4626 tokens, and yield-bearing vaults are, and how to write a yield-bearing vaults smart contract for ERC-4626 tokens.
You can also read the documentation I wrote on the Ethereum official website for ERC-4626. Feel free to leave a comment in the comment section below! 🥂
LogRocket is like a DVR for web and mobile apps, recording everything that happens in your web app or site. Instead of guessing why problems happen, you can aggregate and report on key frontend performance metrics, replay user sessions along with application state, log network requests, and automatically surface all errors.
Modernize how you debug web and mobile apps — Start monitoring for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowSOLID principles help us keep code flexible. In this article, we’ll examine all of those principles and their implementation using JavaScript.
JavaScript’s Date API has many limitations. Explore alternative libraries like Moment.js, date-fns, and the new Temporal API.
Explore use cases for using npm vs. npx such as long-term dependency management or temporary tasks and running packages on the fly.
Validating and auditing AI-generated code reduces code errors and ensures that code is compliant.
One Reply to "How to write an ERC-4626 token contract for yield-bearing vaults"
Great tutorial – really enjoyed it!
One question – what is the purpose of the ‘shareHolder’ mapping here:
// a mapping that checks if a user has deposited
mapping(address => uint256) shareHolder;
I would think that since each user is issued a vToken after deposit, there is really no explicit need to track deposits anymore, but I may be overlooking something. Hope to build a vault smart contract based on 4626 soon. Thanks!