ERC-4626
Implementation of the ERC-4626 "Tokenized Vault Standard" as defined in ERC-4626.
This extension allows the minting and burning of "shares" (represented using the ERC-20 inheritance) in exchange for underlying "assets" through standardized deposit
, mint
, redeem
and burn
workflows. This contract extends the ERC-20 standard. Any additional extensions included along it would affect the "shares" token represented by this contract and not the "assets" token which is an independent contract.
Security concern: Inflation attack
To read more about the security concerns associated with the ERC-4626, check the Inflation attack description.
Usage
In order to make ERC-4626
methods “external” so that other contracts can call them, you need to implement them by yourself for your final contract as follows:
use openzeppelin_stylus::token::erc20::{
self,
extensions::{erc4626, Erc20Metadata, Erc4626, IErc4626},
Erc20,
};
#[derive(SolidityError, Debug)]
enum Error {
Erc4626(erc4626::Error),
Erc20(erc20::Error),
}
#[entrypoint]
#[storage]
struct Erc4626Example {
#[borrow]
erc20: Erc20,
#[borrow]
metadata: Erc20Metadata,
#[borrow]
erc4626: Erc4626,
}
#[public]
#[inherit(Erc20, Erc20Metadata)]
impl Erc4626Example {
fn decimals(&self) -> U8 {
self.erc4626.decimals()
}
fn asset(&self) -> Address {
self.erc4626.asset()
}
fn total_assets(&mut self) -> Result<U256, Error> {
Ok(self.erc4626.total_assets()?)
}
fn convert_to_shares(&mut self, assets: U256) -> Result<U256, Error> {
Ok(self.erc4626.convert_to_shares(assets, &self.erc20)?)
}
fn convert_to_assets(&mut self, shares: U256) -> Result<U256, Error> {
Ok(self.erc4626.convert_to_assets(shares, &self.erc20)?)
}
fn max_deposit(&self, receiver: Address) -> U256 {
self.erc4626.max_deposit(receiver)
}
fn preview_deposit(&mut self, assets: U256) -> Result<U256, Error> {
Ok(self.erc4626.preview_deposit(assets, &self.erc20)?)
}
fn deposit(
&mut self,
assets: U256,
receiver: Address,
) -> Result<U256, Error> {
Ok(self.erc4626.deposit(assets, receiver, &mut self.erc20)?)
}
fn max_mint(&self, receiver: Address) -> U256 {
self.erc4626.max_mint(receiver)
}
fn preview_mint(&mut self, shares: U256) -> Result<U256, Error> {
Ok(self.erc4626.preview_mint(shares, &self.erc20)?)
}
fn mint(&mut self, shares: U256, receiver: Address) -> Result<U256, Error> {
Ok(self.erc4626.mint(shares, receiver, &mut self.erc20)?)
}
fn max_withdraw(&mut self, owner: Address) -> Result<U256, Error> {
Ok(self.erc4626.max_withdraw(owner, &self.erc20)?)
}
fn preview_withdraw(&mut self, assets: U256) -> Result<U256, Error> {
Ok(self.erc4626.preview_withdraw(assets, &self.erc20)?)
}
fn withdraw(
&mut self,
assets: U256,
receiver: Address,
owner: Address,
) -> Result<U256, Error> {
Ok(self.erc4626.withdraw(assets, receiver, owner, &mut self.erc20)?)
}
fn max_redeem(&self, owner: Address) -> U256 {
self.erc4626.max_redeem(owner, &self.erc20)
}
fn preview_redeem(&mut self, shares: U256) -> Result<U256, Error> {
Ok(self.erc4626.preview_redeem(shares, &self.erc20)?)
}
fn redeem(
&mut self,
shares: U256,
receiver: Address,
owner: Address,
) -> Result<U256, Error> {
Ok(self.erc4626.redeem(shares, receiver, owner, &mut self.erc20)?)
}
}
Additionally, you need to ensure proper initialization during contract deployment. Make sure to include the following code in your Solidity Constructor:
contract Erc4626Example {
// Erc20 Token Storage
mapping(address account => uint256) private _balances;
mapping(address account => mapping(address spender => uint256))
private _allowances;
uint256 private _totalSupply;
// Erc20 Metadata Storage
string private _name;
string private _symbol;
// Erc4626 Storage
address private _asset;
uint8 private _underlyingDecimals;
uint8 private _decimalsOffset;
constructor(string memory name_, string memory symbol_, address asset_, uint8 decimalsOffset_) {
_name = name_;
_symbol = symbol_;
_asset = asset_;
_decimalsOffset = decimalsOffset_;
_underlyingDecimals = 18;
}
}