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.”
For Ethereum-based applications, ERCs (Ethereum Request for Comments) are standards that include name registries, package formats, libraries, and token 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.
What are yield-bearing vaults?
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.
Use cases for yield-bearing vaults
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:
Funding: DAOs and governments employ yield-bearing vaults to raise funding without crowdsourcing
Crypto lending: companies like Yearn Finance provide protocols that allow users to maximize their profits by lending and selling crypto assets
DCA (Dollar Cost Averaging) vaults: DCA vaults employ yield-bearing strategies to optimize profits
What is ERC-4626?
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:
Deposits and withdrawals
Vault balances
Interfaces
Events
History of ERC-4626
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.”
How to write an ERC-4626 vault contract
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.
Conclusion
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! 🥂