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::{
        extensions::{
            erc4626, Erc20Metadata, Erc4626, IErc20Metadata, IErc4626,
        },
        Erc20, IErc20,
    },
    utils::introspection::erc165::IErc165,
};

#[entrypoint]
#[storage]
struct Erc4626Example {
    erc4626: Erc4626,
    erc20: Erc20,
    metadata: Erc20Metadata,
}

#[public]
#[implements(IErc4626<Error = erc4626::Error>, IErc20<Error = erc4626::Error>, IErc20Metadata, IErc165)]
impl Erc4626Example {
    #[constructor]
    fn constructor(
        &mut self,
        asset: Address,
        decimals_offset: U8,
        name: String,
        symbol: String,
    ) {
        self.erc4626.constructor(asset, decimals_offset);
        self.metadata.constructor(name, symbol);
    }
}

#[public]
impl IErc4626 for Erc4626Example {
    type Error = erc4626::Error;

    fn asset(&self) -> Address {
        self.erc4626.asset()
    }

    fn total_assets(&mut self) -> Result<U256, Self::Error> {
        self.erc4626.total_assets()
    }

    fn convert_to_shares(
        &mut self,
        assets: U256,
    ) -> Result<U256, Self::Error> {
        self.erc4626.convert_to_shares(assets, &self.erc20)
    }

    fn convert_to_assets(
        &mut self,
        shares: U256,
    ) -> Result<U256, Self::Error> {
        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, Self::Error> {
        self.erc4626.preview_deposit(assets, &self.erc20)
    }

    fn deposit(
        &mut self,
        assets: U256,
        receiver: Address,
    ) -> Result<U256, Self::Error> {
        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, Self::Error> {
        self.erc4626.preview_mint(shares, &self.erc20)
    }

    fn mint(
        &mut self,
        shares: U256,
        receiver: Address,
    ) -> Result<U256, Self::Error> {
        self.erc4626.mint(shares, receiver, &mut self.erc20)
    }

    fn max_withdraw(
        &mut self,
        owner: Address,
    ) -> Result<U256, Self::Error> {
        self.erc4626.max_withdraw(owner, &self.erc20)
    }

    fn preview_withdraw(
        &mut self,
        assets: U256,
    ) -> Result<U256, Self::Error> {
        self.erc4626.preview_withdraw(assets, &self.erc20)
    }

    fn withdraw(
        &mut self,
        assets: U256,
        receiver: Address,
        owner: Address,
    ) -> Result<U256, Self::Error> {
        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, Self::Error> {
        self.erc4626.preview_redeem(shares, &self.erc20)
    }

    fn redeem(
        &mut self,
        shares: U256,
        receiver: Address,
        owner: Address,
    ) -> Result<U256, Self::Error> {
        self.erc4626.redeem(shares, receiver, owner, &mut self.erc20)
    }
}

#[public]
impl IErc20 for Erc4626Example {
    type Error = erc4626::Error;

    fn total_supply(&self) -> U256 {
        self.erc20.total_supply()
    }

    fn balance_of(&self, account: Address) -> U256 {
        self.erc20.balance_of(account)
    }

    fn transfer(
        &mut self,
        to: Address,
        value: U256,
    ) -> Result<bool, Self::Error> {
        Ok(self.erc20.transfer(to, value)?)
    }

    fn allowance(&self, owner: Address, spender: Address) -> U256 {
        self.erc20.allowance(owner, spender)
    }

    fn approve(
        &mut self,
        spender: Address,
        value: U256,
    ) -> Result<bool, Self::Error> {
        Ok(self.erc20.approve(spender, value)?)
    }

    fn transfer_from(
        &mut self,
        from: Address,
        to: Address,
        value: U256,
    ) -> Result<bool, Self::Error> {
        Ok(self.erc20.transfer_from(from, to, value)?)
    }
}

#[public]
impl IErc20Metadata for Erc4626Example {
    fn name(&self) -> String {
        self.metadata.name()
    }

    fn symbol(&self) -> String {
        self.metadata.symbol()
    }

    fn decimals(&self) -> U8 {
        self.erc4626.decimals()
    }
}

#[public]
impl IErc165 for Erc4626Example {
    fn supports_interface(&self, interface_id: FixedBytes<4>) -> bool {
        <Self as IErc4626>::interface_id() == interface_id
            || self.erc20.supports_interface(interface_id)
            || self.metadata.supports_interface(interface_id)
    }
}