ERC20
This module provides interfaces, presets, and utilities related to ERC20 contracts.
For an overview of ERC20, read our ERC20 guide.
Interfaces
Starting from version 3.x.x
, the interfaces are no longer part of the openzeppelin_access
package. The references
documented here are contained in the openzeppelin_interfaces
package version 2.1.0-alpha.0
.
use openzeppelin_interfaces::erc20::IERC20;
Interface of the IERC20 standard as defined in EIP-20.
Functions
total_supply()
balance_of(account)
allowance(owner, spender)
transfer(recipient, amount)
transfer_from(sender, recipient, amount)
approve(spender, amount)
Events
Functions
total_supply() → u256
external
#Returns the amount of tokens in existence.
balance_of(account: ContractAddress) → u256
external
#Returns the amount of tokens owned by account
.
allowance(owner: ContractAddress, spender: ContractAddress) → u256
external
#Returns the remaining number of tokens that spender
is allowed to spend on behalf of owner
through transfer_from. This is zero by default.
This value changes when approve or transfer_from are called.
Events
Transfer(from: ContractAddress, to: ContractAddress, value: u256)
event
#Emitted when value
tokens are moved from one address (from
) to another (to
).
Note that value
may be zero.
Approval(owner: ContractAddress, spender: ContractAddress, value: u256)
event
#Emitted when the allowance of a spender
for an owner
is set. value
is the new allowance.
use openzeppelin_interfaces::erc20::IERC20Metadata;
Interface for the optional metadata functions in EIP-20.
Functions
Functions
name() → ByteArray
external
#Returns the name of the token.
symbol() → ByteArray
external
#Returns the ticker symbol of the token.
decimals() → u8
external
#Returns the number of decimals the token uses - e.g. 8
means to divide the token amount by 100000000
to get its user-readable representation.
For example, if decimals
equals 2
, a balance of 505
tokens should be displayed to a user as 5.05
(505 / 10 ** 2
).
Tokens usually opt for a value of 18
, imitating the relationship between Ether and Wei. This is the default value returned by this function. To create a custom decimals implementation, see Customizing decimals.
This information is only used for display purposes: it in no way affects any of the arithmetic of the contract.
use openzeppelin_interfaces::erc20::IERC20Permit;
Interface of the ERC20Permit standard to support gasless token approvals as defined in EIP-2612.
Functions
Functions
permit(owner: ContractAddress, spender: ContractAddress, amount: u256, deadline: u64, signature: Span<felt252>)
external
#Sets amount
as the allowance of spender
over owner
's tokens after validating the signature.
nonces(owner: ContractAddress) → felt252
external
#Returns the current nonce of owner
. A nonce value must be included whenever a signature for permit
call is generated.
use openzeppelin_interfaces::erc4626::IERC4626;
Interface of the IERC4626 standard as defined in EIP-4626.
Functions
asset()
total_assets()
convert_to_shares(assets)
convert_to_assets(shares)
max_deposit(receiver)
preview_deposit(assets)
deposit(assets, receiver)
max_mint(receiver)
preview_mint(shares)
mint(shares, receiver)
max_withdraw(owner)
preview_withdraw(assets)
withdraw(assets, receiver, owner)
max_redeem(owner)
preview_redeem(shares)
redeem(shares, receiver, owner)
Events
Functions
asset() → ContractAddress
external
#Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.
Requirements:
- MUST be an ERC20 token contract.
- MUST NOT panic.
total_assets() → u256
external
#Returns the total amount of the underlying asset that is "managed" by Vault.
Requirements:
- SHOULD include any compounding that occurs from yield.
- MUST be inclusive of any fees that are charged against assets in the Vault.
- MUST NOT panic.
convert_to_assets(shares: u256) → u256
external
#Returns the amount of assets that the Vault would exchange for the amount of shares
provided irrespective of slippage or fees.
Requirements:
- MUST NOT be inclusive of any fees that are charged against assets in the Vault.
- MUST NOT show any variations depending on the caller.
- MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
- MUST NOT panic unless due to integer overflow caused by an unreasonably large input.
- MUST round down towards 0.
This calculation MAY NOT reflect the "per-user" price-per-share, and instead should reflect the "average-user's" price-per-share, meaning what the average user should expect to see when exchanging to and from.
max_deposit(receiver: ContractAddress) → u256
external
#Returns the maximum amount of the underlying asset that can be deposited into the Vault for receiver
, through a deposit call.
Requirements:
- MUST return a limited value if receiver is subject to some deposit limit.
- MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.
- MUST NOT panic.
preview_deposit(assets: u256) → u256
external
#Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given current on-chain conditions.
Requirements:
- MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit call in the same transaction i.e. IERC4626::deposit should return the same or more shares as
preview_deposit
if called in the same transaction. - MUST NOT account for deposit limits like those returned from IERC4626::max_deposit and should always act as though the deposit would be accepted, regardless if the user has enough tokens approved, etc.
- MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
- MUST NOT panic.
Any unfavorable discrepancy between IERC4626::convert_to_shares and preview_deposit
SHOULD be considered slippage in share price or some other type of condition, meaning the depositor will lose assets by depositing.
deposit(assets: u256, receiver: ContractAddress) → u256
external
#Mints Vault shares to receiver
by depositing exactly amount of assets
.
Requirements:
- MUST emit the IERC4626::Deposit event.
- MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the deposit execution, and are accounted for during deposit.
- MUST panic if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not approving enough underlying tokens to the Vault contract, etc).
Most implementations will require pre-approval of the Vault with the Vault's underlying asset token.
max_mint(receiver: ContractAddress) → u256
external
#Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.
Requirements:
- MUST return a limited value if receiver is subject to some mint limit.
- MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.
- MUST NOT panic.
preview_mint(shares: u256) → u256
external
#Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given current on-chain conditions.
Requirements:
- MUST return as close to and no fewer than the exact amount of assets that would be deposited in a
mint
call in the same transaction. I.e. IERC4626::mint should return the same or fewer assets aspreview_mint
if called in the same transaction. - MUST NOT account for mint limits like those returned from IERC4626::max_mint and should always act as though the mint would be accepted, regardless if the user has enough tokens approved, etc.
- MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
- MUST NOT panic.
Any unfavorable discrepancy between IERC4626::convert_to_assets and preview_mint
SHOULD be considered slippage in share price or some other type of condition, meaning the depositor will lose assets by minting.
mint(shares: u256, receiver: ContractAddress) → u256
external
#Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.
Requirements:
- MUST emit the IERC4626::Deposit event.
- MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint execution, and are accounted for during mint.
- MUST panic if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not approving enough underlying tokens to the Vault contract, etc).
Most implementations will require pre-approval of the Vault with the Vault's underlying asset token.
max_withdraw(owner: ContractAddress) → u256
external
#Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the Vault, through a withdraw call.
Requirements:
- MUST return a limited value if owner is subject to some withdrawal limit or timelock.
- MUST NOT panic.
preview_withdraw(assets: u256) → u256
external
#Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, given current on-chain conditions.
Requirements:
- MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw call in the same transaction i.e. IERC4626::withdraw should return the same or fewer shares as
preview_withdraw
if called in the same transaction. - MUST NOT account for withdrawal limits like those returned from IERC4626::max_withdraw and should always act as though the withdrawal would be accepted, regardless if the user has enough shares, etc.
- MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
- MUST NOT panic.
Any unfavorable discrepancy between IERC4626::convert_to_shares and preview_withdraw
SHOULD be considered slippage in share price or some other type of condition, meaning the depositor will lose assets by depositing.
withdraw(assets: u256, receiver: ContractAddress, owner: ContractAddress) → u256
external
#Burns shares from owner and sends exactly assets of underlying tokens to receiver.
Requirements:
- MUST emit the IERC4626::Withdraw event.
- MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the withdraw execution, and are accounted for during withdraw.
- MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner not having enough shares, etc).
Some implementations will require pre-requesting to the Vault before a withdrawal may be performed. Those methods should be performed separately.
max_redeem(owner: ContractAddress) → u256
external
#Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, through a redeem call.
Requirements:
- MUST return a limited value if owner is subject to some withdrawal limit or timelock.
- MUST return
ERC20::balance_of(owner)
ifowner
is not subject to any withdrawal limit or timelock. - MUST NOT panic.
preview_redeem(shares: u256) → u256
external
#Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, given current on-chain conditions.
Requirements:
- MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call in the same transaction i.e. IERC4626::redeem should return the same or more assets as preview_redeem if called in the same transaction.
- MUST NOT account for redemption limits like those returned from IERC4626::max_redeem and should always act as though the redemption would be accepted, regardless if the user has enough shares, etc.
- MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
- MUST NOT panic.
Any unfavorable discrepancy between IERC4626::convert_to_assets and preview_redeem
SHOULD be considered slippage in share price or some other type of condition, meaning the depositor will lose assets by redeeming.
redeem(shares: u256, receiver: ContractAddress, owner: ContractAddress) → u256
external
#Burns exactly shares from owner and sends assets of underlying tokens to receiver.
Requirements:
- MUST emit the IERC4626::Withdraw event.
- MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the redeem execution, and are accounted for during redeem.
- MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner not having enough shares, etc).
Some implementations will require pre-requesting to the Vault before a withdrawal may be performed. Those methods should be performed separately.
Events
Deposit(sender: ContractAddress, owner: ContractAddress, assets: u256, shares: u256)
event
#Emitted when sender
exchanges assets
for shares
and transfers those shares
to owner
.
Withdraw(sender: ContractAddress, receiver: ContractAddress, owner: ContractAddress, assets: u256, shares: u256)
event
#Emitted when sender
exchanges shares
, owned by owner
, for assets
and transfers those assets
to receiver
.
Core
use openzeppelin_token::erc20::ERC20Component;
ERC20 component extending IERC20 and IERC20Metadata.
See Hooks to understand how are hooks used.
Hooks
ERC20HooksTrait
Embeddable Mixin Implementations
ERC20MixinImpl
Embeddable Implementations
ERC20Impl
total_supply(self)
balance_of(self, account)
allowance(self, owner, spender)
transfer(self, recipient, amount)
transfer_from(self, sender, recipient, amount)
approve(self, spender, amount)
ERC20MetadataImpl
ERC20CamelOnlyImpl
ERC20PermitImpl
permit(self, owner, spender, amount, deadline, signature)
nonces(self, owner)
DOMAIN_SEPARATOR(self)
SNIP12MetadataExternalImpl
Internal implementations
InternalImpl
initializer(self, name, symbol)
mint(self, recipient, amount)
burn(self, account, amount)
update(self, from, to, amount)
_transfer(self, sender, recipient, amount)
_approve(self, owner, spender, amount)
_spend_allowance(self, owner, spender, amount)
Events
Hooks
Hooks are functions which implementations can extend the functionality of the component source code. Every contract using ERC20Component is expected to provide an implementation of the ERC20HooksTrait. For basic token contracts, an empty implementation with no logic must be provided.
You can use openzeppelin_token::erc20::ERC20HooksEmptyImpl
which is already available as part of the library for this purpose.
Embeddable functions
total_supply(@self: ContractState) → u256
external
#See IERC20::total_supply.
balance_of(@self: ContractState, account: ContractAddress) → u256
external
#See IERC20::balance_of.
allowance(@self: ContractState, owner: ContractAddress, spender: ContractAddress) → u256
external
#See IERC20::allowance.
transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) → bool
external
#See IERC20::transfer.
Requirements:
recipient
cannot be the zero address.- The caller must have a balance of at least
amount
.
transfer_from(ref self: ContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256) → bool
external
#Requirements:
sender
cannot be the zero address.sender
must have a balance of at leastamount
.recipient
cannot be the zero address.- The caller must have allowance for
sender
's tokens of at leastamount
.
approve(ref self: ContractState, spender: ContractAddress, amount: u256) → bool
external
#name() → ByteArray
external
#See IERC20Metadata::name.
symbol() → ByteArray
external
#decimals() → u8
external
#totalSupply(self: @ContractState) → u256
external
#See IERC20::total_supply.
Supports the Cairo v0 convention of writing external methods in camelCase as discussed here.
balanceOf(self: @ContractState, account: ContractAddress) → u256
external
#See IERC20::balance_of.
Supports the Cairo v0 convention of writing external methods in camelCase as discussed here.
permit(ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256, deadline: u64, signature: Span<felt252>) → bool
external
#Sets amount
as the allowance of spender
over owner
's tokens after validating the signature.
Requirements:
owner
is a deployed account contract.spender
is not the zero address.deadline
is not a timestamp in the past.signature
is a valid signature that can be validated with a call toowner
account.signature
must use the current nonce of theowner
.
Emits an Approval event. Every successful call increases `owner's nonce by one.
nonces(self: @ContractState, owner: ContractAddress) → felt252
external
#Returns the current nonce of owner
. A nonce value must be included whenever a signature for permit
call is generated.
snip12_metadata(self: @ContractState) → (felt252, felt252)
external
#Returns the domain name and version used to generate the message hash for permit signature.
The returned tuple contains:
t.0
: The name used in the SNIP12Metadata implementation.t.1
: The version used in the SNIP12Metadata implementation.
Internal functions
initializer(ref self: ContractState, name: ByteArray, symbol: ByteArray)
internal
#Initializes the contract by setting the token name and symbol. This should be used inside of the contract's constructor.
update(ref self: ContractState, from: ContractAddress, to: ContractAddress, amount: u256)
internal
#Transfers an amount
of tokens from from
to to
, or alternatively mints (or burns) if from
(or to
) is the zero address.
This function can be extended using the ERC20HooksTrait, to add functionality before and/or after the transfer, mint, or burn.
Emits a Transfer event.
_transfer(ref self: ContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256)
internal
#Moves amount
of tokens from from
to to
.
This internal function does not check for access permissions but can be useful as a building block, for example to implement automatic token fees, slashing mechanisms, etc.
Emits a Transfer event.
Requirements:
from
cannot be the zero address.to
cannot be the zero address.from
must have a balance of at leastamount
.
_approve(ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256)
internal
#Sets amount
as the allowance of spender
over owner
's tokens.
This internal function does not check for access permissions but can be useful as a building block, for example to implement automatic allowances on behalf of other addresses.
Emits an Approval event.
Requirements:
owner
cannot be the zero address.spender
cannot be the zero address.
_spend_allowance(ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256)
internal
#Updates owner
's allowance for spender
based on spent amount
.
This internal function does not update the allowance value in the case of infinite allowance.
Possibly emits an Approval event.
Events
Transfer(from: ContractAddress, to: ContractAddress, value: u256)
event
#See IERC20::Transfer.
Approval(owner: ContractAddress, spender: ContractAddress, value: u256)
event
#See IERC20::Approval.
Extensions
use openzeppelin_token::erc20::extensions::erc4626::ERC4626Component;
Extension of ERC20 that implements the IERC4626 interface which allows the minting and burning of "shares" in exchange for an underlying "asset." The component leverages traits to configure fees, limits, and decimals.
constants
functions
Hooks
FeeConfigTrait
calculate_deposit_fee(self, assets, shares)
calculate_mint_fee(self, assets, shares)
calculate_withdraw_fee(self, assets, shares)
calculate_redeem_fee(self, assets, shares)
LimitConfigTrait
deposit_limit(self, receiver)
mint_limit(self, receiver)
withdraw_limit(self, owner)
redeem_limit(self, owner)
ERC4626HooksTrait
before_deposit(self, caller, receiver, assets, shares, fee)
after_deposit(self, caller, receiver, assets, shares, fee)
before_withdraw(self, caller, receiver, owner, assets, shares, fee)
after_withdraw(self, caller, receiver, owner, assets, shares, fee)
AssetsManagementTrait
Embeddable Implementations
ERC4626Impl
asset(self)
total_assets(self)
convert_to_shares(self, assets)
convert_to_assets(self, shares)
max_deposit(self, receiver)
preview_deposit(self, assets)
deposit(self, assets, receiver)
max_mint(self, receiver)
preview_mint(self, shares)
mint(self, shares, receiver)
max_withdraw(self, owner)
preview_withdraw(self, assets)
withdraw(self, assets, receiver, owner)
max_redeem(self, owner)
preview_redeem(self, shares)
redeem(self, shares, receiver, owner)
ERC20Impl
total_supply(self)
balance_of(self, account)
allowance(self, owner, spender)
transfer(self, recipient, amount)
transfer_from(self, sender, recipient, amount)
approve(self, spender, amount)
ERC4626MetadataImpl
Internal functions
InternalImpl
initializer(self, asset_address)
_deposit(self, caller, receiver, assets, shares)
_withdraw(self, caller, receiver, owner, assets, shares)
_convert_to_shares(self, assets, rounding)
_convert_to_assets(self, shares, rounding)
Immutable Config
UNDERLYING_DECIMALS: u128
constant
#Should match the underlying asset's decimals. The default value is 18
.
DECIMALS_OFFSET: u128
constant
#Corresponds to the representational offset between UNDERLYING_DECIMALS
and the vault decimals. The greater the offset, the more expensive it is for attackers to execute an inflation attack.
validate()
internal
#Validates the given implementation of the contract's configuration.
Requirements:
UNDERLYING_DECIMALS
+DECIMALS_OFFSET
cannot exceed 255 (max u8).
This function is called by the contract's initializer.
Hooks
Hooks are functions which implementations can extend the functionality of the component source code. Every contract using ERC4626Component is expected to provide an implementation of the ERC4626HooksTrait. For basic token contracts, an empty implementation with no logic must be provided.
You can use openzeppelin_token::erc20::extensions::erc4626::ERC4626EmptyHooks
which is already available as part of the library for this purpose.
FeeConfigTrait
The logic for calculating entry and exit fees is expected to be defined at the contract level. Defaults to no entry or exit fees.
The FeeConfigTrait hooks directly into the preview methods of the ERC4626 component. The preview methods must return as close to the exact amount of shares or assets as possible if the actual (previewed) operation occurred in the same transaction (according to EIP-4626 spec). All operations use their corresponding preview method as the value of assets or shares being moved to or from the user. The fees calculated in FeeConfigTrait are used to adjust the final asset and share amounts used in both the preview and the actual operations.
To transfer fees, this trait needs to be coordinated with ERC4626Component::ERC4626Hooks
.
See implementation examples:
- Contract charging fees in assets: ERC4626AssetsFeesMock
- Contract charging fees in shares: ERC4626SharesFeesMock
calculate_deposit_fee(self: @ContractState, assets: u256, shares: u256) → Option<Fee>
hook
#Calculates the entry fee for a deposit during preview_deposit. The returned fee affects the final asset and share amounts. Fees are not transferred automatically and must be handled in the after_deposit hook: asset fees should be transferred from the vault's management to the fee recipient, while share fees should be minted to the fee recipient.
calculate_mint_fee(self: @ContractState, assets: u256, shares: u256) → Option<Fee>
hook
#Calculates the entry fee for a mint during preview_mint. The returned fee affects the final asset and share amounts. Fees are not transferred automatically and must be handled in the after_deposit hook: asset fees should be transferred from the vault's management to the fee recipient, while share fees should be minted to the fee recipient.
calculate_withdraw_fee(self: @ContractState, assets: u256, shares: u256) → Option<Fee>
hook
#Calculates the exit fee for a withdraw during preview_withdraw. The returned fee affects the final asset and share amounts. Fees are not transferred automatically and must be handled in the before_withdraw hook: asset fees should be transferred from the vault's management to the fee recipient, while share fees should be transferred from the owner to the fee recipient.
calculate_redeem_fee(self: @ContractState, assets: u256, shares: u256) → Option<Fee>
hook
#Calculates the exit fee for a redeem during preview_redeem. The returned fee affects the final asset and share amounts. Fees are not transferred automatically and must be handled in the before_withdraw hook: asset fees should be transferred from the vault's management to the fee recipient, while share fees should be transferred from the owner to the fee recipient.
LimitConfigTrait
Sets limits to the target exchange type and is expected to be defined at the contract level. These limits correspond directly to the max_<OPERATION>
i.e. deposit_limit
→ max_deposit
.
The EIP-4626 spec states that the max_<OPERATION>
methods must take into account all global and user-specific limits. If an operation is disabled (even temporarily), the corresponding limit MUST be 0
and MUST NOT panic.
deposit_limit(ref self: ContractState, receiver: ContractAddress) → Option<u256>
hook
#The max deposit allowed.
Defaults (Option::None
) to 2 ** 256 - 1.
mint_limit(ref self: ContractState, receiver: ContractAddress) → Option<u256>
hook
#The max mint allowed.
Defaults (Option::None
) to 2 ** 256 - 1.
withdraw_limit(ref self: ContractState, owner: ContractAddress) → Option<u256>
hook
#The max withdraw allowed.
Defaults (Option::None
) to the full asset balance of owner
converted from shares.
redeem_limit(ref self: ContractState, owner: ContractAddress) → Option<u256>
hook
#The max redeem allowed.
Defaults (Option::None
) to the full asset balance of owner
.
ERC4626HooksTrait
Allows contracts to hook logic into deposit and withdraw transactions. This is where contracts can transfer fees.
ERC4626 preview methods must be inclusive of any entry or exit fees. Fees are calculated using FeeConfigTrait methods and automatically adjust the final asset and share amounts. Fee transfers are handled in ERC4626HooksTrait
methods.
Special care must be taken when calling external contracts in these hooks. In that case, consider implementing reentrancy protections. For example, in the withdraw
flow, the withdraw_limit
is checked before the before_withdraw
hook is invoked. If this hook performs a reentrant call that invokes withdraw
again, the subsequent check on withdraw_limit
will be done before the first withdrawal's core logic (e.g., burning shares and transferring assets) is executed. This could lead to bypassing withdrawal constraints or draining funds.
See the ERC4626AssetsFeesMock and ERC4626SharesFeesMock examples.
before_deposit(ref self: ContractState, caller: ContractAddress, receiver: ContractAddress, assets: u256, shares: u256, fee: Option<Fee>)
hook
#Hooks into _deposit.
Executes logic before transferring assets and minting shares. The fee is calculated via FeeConfigTrait. Assets and shares represent the actual amounts the user will spend and receive, respectively. Asset fees are included in assets; share fees are excluded from shares.
after_deposit(ref self: ContractState, caller: ContractAddress, receiver: ContractAddress, assets: u256, shares: u256, fee: Option<Fee>)
hook
#Hooks into _deposit.
Executes logic after transferring assets and minting shares. The fee is calculated via FeeConfigTrait. Assets and shares represent the actual amounts the user will spend and receive, respectively. Asset fees are included in assets; share fees are excluded from shares.
before_withdraw(ref self: ContractState, caller: ContractAddress, receiver: ContractAddress, owner: ContractAddress, assets: u256, shares: u256, fee: Option<Fee>)
hook
#Hooks into _withdraw.
Executes logic before burning shares and transferring assets. The fee is calculated via FeeConfigTrait. Assets and shares represent the actual amounts the user will receive and spend, respectively. Asset fees are excluded from assets; share fees are included in shares.
after_withdraw(ref self: ContractState, caller: ContractAddress, receiver: ContractAddress, owner: ContractAddress, assets: u256, shares: u256, fee: Option<Fee>)
hook
#Hooks into _withdraw.
Executes logic after burning shares and transferring assets. The fee is calculated via FeeConfigTrait. Assets and shares represent the actual amounts the user will receive and spend, respectively. Asset fees are excluded from assets; share fees are included in shares.
AssetsManagementTrait
Defines how the ERC4626 vault manages its underlying assets. This trait provides the core asset management functionality for the vault, abstracting the actual storage and transfer mechanisms. It enables two primary implementation patterns:
- Self-managed assets: The vault contract holds assets directly on its own address. This is the default behavior provided by
ERC4626SelfAssetsManagement
implementation. - External vault: Assets are managed by an external contract, allowing for more complex asset management strategies. The exact implementation is expected to be defined by the contract implementing the ERC4626 component.
The trait methods are called during deposit, withdrawal, and total assets calculations, ensuring that the vault's share pricing remains accurate regardless of the underlying asset management strategy.
Implementations must ensure that get_total_assets
returns the actual amount of assets that can be withdrawn by users. Inaccurate reporting can lead to incorrect share valuations and potential economic attacks.
See implementation examples:
- Self-managed vault: ERC4626SelfAssetsManagement.
- External vault: ERC4626ExternalAssetsManagement.
get_total_assets(self: @ContractState) → u256
hook
#Returns the total amount of underlying assets under the vault's management. Used for share price calculations and determining the vault's total value.
This method should return the actual amount of assets that the vault controls and that can be used to satisfy withdrawal requests. For self-managed vaults, this is typically the vault contract's token balance. For external vaults, this should include any assets deposited in external protocols, minus any that are locked or unredeemable.
The accuracy of this method is critical for proper vault operation: - Overreporting can lead to share dilution and user losses. - Underreporting can lead to share inflation and potential economic attacks.
transfer_assets_in(ref self: ContractState, from: ContractAddress, assets: u256)
hook
#Transfers assets from an external address into the vault's management. Called during deposit
and mint
operations.
This method should handle the actual transfer of underlying assets from the from
address into the vault's control. For self-managed vaults, this typically means transferring tokens to the vault contract's address. For external vaults, this might involve transferring into an external contract.
Requirements:
- MUST transfer exactly
assets
amount of the underlying token. - SHOULD revert if the transfer fails or insufficient allowance/balance.
transfer_assets_out(ref self: ContractState, to: ContractAddress, assets: u256)
hook
#Transfers assets from the vault's management to an external address. Called during withdraw and redeem operations.
This method should handle the actual transfer of underlying assets from the vault's control to the to
address. For self-managed vaults, this typically means transferring tokens from the vault contract's address. For external vaults, this might involve withdrawing from an external contract first.
Requirements:
- MUST transfer exactly
assets
amount of the underlying token. - SHOULD revert if insufficient assets are available or transfer fails.
Embeddable functions
asset(self: @ContractState) → ContractAddress
external
#Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.
total_assets(self: @ContractState) → u256
external
#Returns the total amount of the underlying asset that is "managed" by Vault.
max_deposit(self: @ContractState, receiver: ContractAddress) → u256
external
#Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver
, through a deposit call.
The default max deposit value is 2 ** 256 - 1.
This can be changed in the implementing contract by defining custom logic in LimitConfigTrait::deposit_limit.
preview_deposit(self: @ContractState, assets: u256) → u256
external
#Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given current on-chain conditions.
The default deposit preview value is the full amount of shares. This can be changed to account for fees, for example, in the implementing contract by defining custom logic in FeeConfigTrait::calculate_deposit_fee.
This method must be inclusive of entry fees to be compliant with the EIP-4626 spec.
deposit(ref self: ContractState, assets: u256, receiver: ContractAddress) → u256
external
#Mints Vault shares to receiver
by depositing exactly assets
of underlying tokens. Returns the amount of newly-minted shares.
Requirements:
assets
is less than or equal to the max deposit amount forreceiver
.
Emits a Deposit event.
max_mint(self: @ContractState, receiver: ContractAddress) → u256
external
#Returns the maximum amount of the Vault shares that can be minted for receiver
through a mint call.
The default max mint value is 2 ** 256 - 1.
This can be changed in the implementing contract by defining custom logic in LimitConfigTrait::mint_limit.
preview_mint(self: @ContractState, shares: u256) → u256
external
#Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given current on-chain conditions.
The default mint preview value is the full amount of assets. This can be changed to account for fees, for example, in the implementing contract by defining custom logic in FeeConfigTrait::calculate_mint_fee.
This method must be inclusive of entry fees to be compliant with the EIP-4626 spec.
mint(self: @ContractState, shares: u256, receiver: ContractAddress) → u256
external
#Mints exactly Vault shares
to receiver
by depositing amount of underlying tokens. Returns the amount deposited assets.
Requirements:
shares
is less than or equal to the max shares amount forreceiver
.
Emits a Deposit event.
max_withdraw(self: @ContractState, owner: ContractAddress) → u256
external
#Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the Vault, through a withdraw call.
The default max withdraw value is the full balance of assets for owner
(converted from shares). This can be changed in the implementing contract by defining custom logic in LimitConfigTrait::withdraw_limit.
With customized limits, the maximum withdraw amount will either be the custom limit itself or owner
's total asset balance, whichever value is less.
preview_withdraw(self: @ContractState, assets: u256) → u256
external
#Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, given current on-chain conditions.
The default withdraw preview value is the full amount of shares. This can be changed to account for fees, for example, in the implementing contract by defining custom logic in FeeConfigTrait::calculate_withdraw_fee.
This method must be inclusive of exit fees to be compliant with the EIP-4626 spec.
max_redeem(self: @ContractState, owner: ContractAddress) → u256
external
#Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, through a redeem call.
The default max redeem value is the full balance of assets for owner
. This can be changed in the implementing contract by defining custom logic in LimitConfigTrait::redeem_limit.
With customized limits, the maximum redeem amount will either be the custom limit itself or owner
's total asset balance, whichever value is less.
preview_redeem(self: @ContractState, shares: u256) → u256
external
#Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, given current on-chain conditions.
The default redeem preview value is the full amount of assets. This can be changed to account for fees, for example, in the implementing contract by defining custom logic in FeeConfigTrait::calculate_redeem_fee.
This method must be inclusive of exit fees to be compliant with the EIP-4626 spec.
name(self: @ContractState) → ByteArray
external
#Returns the name of the token.
symbol(self: @ContractState) → ByteArray
external
#Returns the ticker symbol of the token, usually a shorter version of the name.
decimals(self: @ContractState) → u8
external
#Returns the cumulative number of decimals which includes both UNDERLYING_DECIMALS
and OFFSET_DECIMALS
. Both of which must be defined in the ImmutableConfig inside the implementing contract.
Internal functions
initializer(ref self: ContractState, asset_address: ContractAddress)
internal
#Validates the ImmutableConfig constants and sets the asset_address
to the vault. This should be set in the contract's constructor.
Requirements:
asset_address
cannot be the zero address.
_deposit(ref self: ContractState, caller: ContractAddress, receiver: ContractAddress, assets: u256, shares: u256)
internal
#Internal logic for deposit and mint.
Transfers assets
from caller
to the Vault contract then mints shares
to receiver
. Fees can be transferred in the ERC4626Hooks::after_deposit
hook which is executed after assets are transferred and shares are minted.
Requirements:
- ERC20::transfer_from must return true.
Emits two ERC20::Transfer events (ERC20::mint
and ERC20::transfer_from
).
Emits a Deposit event.
_withdraw(ref self: ContractState, caller: ContractAddress, receiver: ContractAddress, owner: ContractAddress, assets: u256, shares: u256)
internal
#Internal logic for withdraw and redeem.
Burns shares
from owner
and then transfers assets
to receiver
. Fees can be transferred in the ERC4626Hooks::before_withdraw
hook which is executed before shares are burned and assets are transferred.
Requirements:
- ERC20::transfer must return true.
Emits two ERC20::Transfer events (ERC20::burn
and ERC20::transfer
).
Emits a Withdraw event.
_convert_to_assets(self: @ContractState, shares: u256, rounding: Rounding) -> u256
internal
#Internal conversion function (from shares to assets) with support for rounding
direction.
Presets
use openzeppelin_presets::ERC20Upgradeable;
Upgradeable ERC20 contract leveraging ERC20Component with a fixed-supply mechanism for token distribution.
0x07802658d99373a4002434cbdc8897d1936c6b1beea48af0cc3b5574707f8d92
Constructor
Embedded Implementations
ERC20MixinImpl
OwnableMixinImpl
External Functions
Constructor
constructor(ref self: ContractState, name: ByteArray, symbol: ByteArray, fixed_supply: u256, recipient: ContractAddress, owner: ContractAddress)
constructor
#Sets the name
and symbol
and mints fixed_supply
tokens to recipient
. Assigns owner
as the contract owner with permissions to upgrade.
External functions
upgrade(ref self: ContractState, new_class_hash: ClassHash)
external
#Upgrades the contract to a new implementation given by new_class_hash
.
Requirements:
- The caller is the contract owner.
new_class_hash
cannot be zero.