Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add erc4626 docs #1244

Draft
wants to merge 71 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
82b3173
start erc4626
andrew-fleming Sep 26, 2024
f08dd48
add fns from interface
andrew-fleming Sep 27, 2024
c558c59
start business logic
andrew-fleming Oct 1, 2024
0b572e5
add mul_div and tests
andrew-fleming Oct 1, 2024
3cadd55
simplify math
andrew-fleming Oct 1, 2024
f66bce4
add fix me comments
andrew-fleming Oct 1, 2024
5d7b1d9
fix fmt
andrew-fleming Oct 1, 2024
33bd0d4
fix fmt
andrew-fleming Oct 1, 2024
ce22c2f
add convert_to logic, add metadata impl
andrew-fleming Oct 2, 2024
4fefde2
reexports
andrew-fleming Oct 2, 2024
80c4013
add erc4626 mock
andrew-fleming Oct 2, 2024
7e4177f
add erc4626 mock
andrew-fleming Oct 2, 2024
3def693
start erc4626 tests
andrew-fleming Oct 2, 2024
77fdfa6
comment out mods and tests to improve performance
andrew-fleming Oct 2, 2024
dc66804
add offset config in mock
andrew-fleming Oct 3, 2024
b05f57e
add overflow assertion and test
andrew-fleming Oct 3, 2024
ea3bf06
add power fn
andrew-fleming Oct 6, 2024
f5ce03b
add erc20reentrant mock
andrew-fleming Oct 6, 2024
e70061d
fix interface fns
andrew-fleming Oct 6, 2024
ca52772
fix logic, add power
andrew-fleming Oct 6, 2024
8072770
add starting tests-no assets, no shares
andrew-fleming Oct 6, 2024
7d2a3c1
fix fmt
andrew-fleming Oct 6, 2024
a45c8dc
clean up power fn
andrew-fleming Oct 6, 2024
bb1cdc2
simplify operation
andrew-fleming Oct 6, 2024
f2681e5
add comments, fix visibility
andrew-fleming Oct 6, 2024
f401df1
move fn, remove tests
andrew-fleming Oct 6, 2024
4bdde8d
add test_math mod
andrew-fleming Oct 6, 2024
ccc8784
add mint to mock vault construction
andrew-fleming Oct 8, 2024
87a84ab
add full vault tests
andrew-fleming Oct 8, 2024
501d7b9
fix fmt
andrew-fleming Oct 8, 2024
ee222f5
fix assertions
andrew-fleming Oct 8, 2024
970cb75
add full vault redeem tests
andrew-fleming Oct 8, 2024
39b205c
fix fmt
andrew-fleming Oct 8, 2024
5af5b8b
fix fmt
andrew-fleming Oct 8, 2024
69c4831
fix conflicts
andrew-fleming Oct 8, 2024
b735b5d
fix fmt
andrew-fleming Oct 8, 2024
43440d0
move erc4626 tests to erc4626 dir
andrew-fleming Oct 8, 2024
56cea65
add transfer assertion
andrew-fleming Oct 9, 2024
0870930
add reentrancy tests
andrew-fleming Oct 11, 2024
65dddfc
expose burn in mock
andrew-fleming Oct 11, 2024
bded58f
tidy up tests
andrew-fleming Oct 11, 2024
fd4d146
add default decimals mock
andrew-fleming Oct 11, 2024
2bf3aac
fix deploy fn names
andrew-fleming Oct 11, 2024
9c7243f
improve helper fn name
andrew-fleming Oct 11, 2024
bb3a6b0
fix fmt
andrew-fleming Oct 11, 2024
c8a9dfa
add comments, remove unused dep
andrew-fleming Oct 19, 2024
385118e
update branch
andrew-fleming Oct 19, 2024
bc88cf8
fix conflicts
andrew-fleming Oct 19, 2024
1d14afc
fix conflicts
andrew-fleming Oct 19, 2024
acc2047
fix fmt
andrew-fleming Oct 19, 2024
2e368fd
update spdx
andrew-fleming Oct 19, 2024
c4f565e
fix conflicts
andrew-fleming Nov 20, 2024
c343716
add reqs to mul_div, fix spdx
andrew-fleming Nov 21, 2024
0e10817
remove duplicate erc4626 in votes
andrew-fleming Nov 21, 2024
cbdd70a
add/fix in-code docs
andrew-fleming Nov 21, 2024
970d621
fix param name
andrew-fleming Nov 22, 2024
1418543
fix comments
andrew-fleming Nov 25, 2024
a10b28c
add multiple tx tests
andrew-fleming Nov 25, 2024
e67d312
fix err msgs
andrew-fleming Nov 25, 2024
ed727df
fix comments, fix test
andrew-fleming Nov 25, 2024
b7fb9de
add note to power
andrew-fleming Nov 26, 2024
44055bd
use math:: prefix in tests
andrew-fleming Nov 26, 2024
8d8e066
improve u256_mul_div
andrew-fleming Nov 26, 2024
450efa4
remove unused var
andrew-fleming Nov 27, 2024
d912c6d
remove line
andrew-fleming Nov 27, 2024
0cd10da
improve var name
andrew-fleming Nov 27, 2024
3ef163f
add changelog entries
andrew-fleming Nov 29, 2024
4fcdd0c
Merge branch 'main' into add-erc4626
andrew-fleming Nov 29, 2024
c8b7161
fix version in spdx
andrew-fleming Nov 29, 2024
bd9ef57
start erc4626 docs
andrew-fleming Dec 1, 2024
199a8a8
add api for erc4626
andrew-fleming Dec 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- ERC4626Component (#1170)
- `Math::u256_mul_div` (#1170)
- SRC9 (Outside Execution) integration to account presets (#1201)
- `SNIP12HashSpanImpl` to `openzeppelin_utils::cryptography::snip12` (#1180)
- GovernorComponent with the following extensions: (#1180)
Expand Down
2 changes: 1 addition & 1 deletion docs/antora.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ nav:
- modules/ROOT/nav.adoc
asciidoc:
attributes:
page-sidebar-collapse-default: 'Access,Accounts,Finance,Governance,Introspection,Security,ERC20,ERC721,ERC1155,Upgrades,Universal Deployer Contract'
page-sidebar-collapse-default: 'Access,Accounts,Finance,Governance,Introspection,Security,ERC20,ERC721,ERC1155,ERC4626,Upgrades,Universal Deployer Contract'
2 changes: 2 additions & 0 deletions docs/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
**** xref:/api/erc721.adoc[API Reference]
*** xref:erc1155.adoc[ERC1155]
**** xref:/api/erc1155.adoc[API Reference]
*** xref:erc4626.adoc[ERC4626]
**** xref:/api/erc4626.adoc[API Reference]
*** xref:/api/token_common.adoc[Common]

** xref:udc.adoc[Universal Deployer Contract]
Expand Down
323 changes: 320 additions & 3 deletions docs/modules/ROOT/pages/api/erc20.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,7 @@ See <<IERC20-Approval,IERC20::Approval>>.
use openzeppelin_token::erc20::interface::IERC20Permit;
```

Interface of the ERC20Permit standard to support gasless token approvals as defined in {eip-2612}.
Interface of the ERC20Permit standard to support gasless token approvals as defined in {eip-2612}.

[.contract-index]
.Functions
Expand Down Expand Up @@ -553,9 +553,326 @@ whenever a signature for `permit` call is generated.
[[IERC20Permit-DOMAIN_SEPARATOR]]
==== `[.contract-item-name]#++DOMAIN_SEPARATOR++#++() → felt252++` [.item-kind]#external#

Returns the domain separator used in generating a message hash for `permit` signature.
Returns the domain separator used in generating a message hash for `permit` signature.
The domain hashing logic follows the {snip-12} standard.

[.contract]
[[ERC4626Component]]
=== `++ERC4626Component++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.20.0-rc.0/packages/token/src/erc20/extensions/erc4626/interface.cairo#L19[{github-icon},role=heading-link]

[.hljs-theme-dark]
```cairo
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.

[.contract-index]
.Hooks
--
[.sub-index#ERC4626Component-ERC4626HooksTrait]
.ERC4626HooksTrait
* xref:#ERC4626Component-before_withdraw[`++before_withdraw(self, assets, shares)++`]
* xref:#ERC4626Component-after_deposit[`++after_deposit(self, assets, shares)++`]
--

[.contract-index#ERC4626Component-Embeddable-Impls]
.Embeddable Implementations
--

.ERC4626Impl
* xref:#ERC4626Component-asset[`++asset(self)++`]
* xref:#ERC4626Component-total_assets[`++total_assets(self)++`]
* xref:#ERC4626Component-convert_to_shares[`++convert_to_shares(self, assets)++`]
* xref:#ERC4626Component-convert_to_assets[`++convert_to_assets(self, shares)++`]
* xref:#ERC4626Component-max_deposit[`++max_deposit(self, receiver)++`]
* xref:#ERC4626Component-preview_deposit[`++preview_deposit(self, assets)++`]
* xref:#ERC4626Component-deposit[`++deposit(self, assets, receiver)++`]
* xref:#ERC4626Component-max_mint[`++max_mint(self, receiver)++`]
* xref:#ERC4626Component-preview_mint[`++preview_mint(self, shares)++`]
* xref:#ERC4626Component-mint[`++mint(self, shares, receiver)++`]
* xref:#ERC4626Component-max_withdraw[`++max_withdraw(self, owner)++`]
* xref:#ERC4626Component-preview_withdraw[`++preview_withdraw(self, assets)++`]
* xref:#ERC4626Component-withdraw[`++withdraw(self, assets, receiver, owner)++`]
* xref:#ERC4626Component-max_redeem[`++max_redeem(self, owner)++`]
* xref:#ERC4626Component-preview_redeem[`++preview_redeem(self, shares)++`]
* xref:#ERC4626Component-redeem[`++redeem(self, shares, receiver, owner)++`]

.ERC20Impl
* xref:#ERC20Component-total_supply[`++total_supply(self)++`]
* xref:#ERC20Component-balance_of[`++balance_of(self, account)++`]
* xref:#ERC20Component-allowance[`++allowance(self, owner, spender)++`]
* xref:#ERC20Component-transfer[`++transfer(self, recipient, amount)++`]
* xref:#ERC20Component-transfer_from[`++transfer_from(self, sender, recipient, amount)++`]
* xref:#ERC20Component-approve[`++approve(self, spender, amount)++`]

.ERC4626MetadataImpl
* xref:#ERC4626Component-name[`++name(self)++`]
* xref:#ERC4626Component-symbol[`++symbol(self)++`]
* xref:#ERC4626Component-decimals[`++decimals(self)++`]

--

[.contract-index]
.Internal functions
--
.InternalImpl
* xref:#ERC4626Component-initializer[`++initializer(self, asset_address)++`]
* xref:#ERC4626Component-_deposit[`++_deposit(self, caller, receiver, assets, shares)++`]
* xref:#ERC4626Component-_withdraw[`++_withdraw(self, caller, receiver, owner, assets, shares)++`]
* xref:#ERC4626Component-_convert_to_shares[`++_convert_to_shares(self, assets, rounding)++`]
* xref:#ERC4626Component-_convert_to_assets[`++_convert_to_assets(self, shares, rounding)++`]
--

[#ERC4626Component-Hooks]
==== 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.

TIP: You can use `openzeppelin_token::erc20::extensions::erc4626::ERC4626HooksEmptyImpl` which is already available as part of the library for this purpose.

[.contract-item]
[[ERC4626Component-before_withdraw]]
==== `[.contract-item-name]#++before_withdraw++#++(ref self: ContractState, assets: u256, shares: u256)++` [.item-kind]#hook#

Function executed at the beginning of the xref:#ERC4626Component-withdraw[withdraw] and xref:#ERC4626Component-redeem[redeem] functions prior to any other logic.

[.contract-item]
[[ERC4626Component-after_deposit]]
==== `[.contract-item-name]#++after_deposit++#++(ref self: ContractState, assets: u256, shares: u256)++` [.item-kind]#hook#

Function executed at the end of the xref:#ERC4626Component-deposit[deposit] and xref:#ERC4626Component-mint[mint] functions after all other logic.

==== Embeddable functions

[.contract-item]
[[ERC4626Component-asset]]
==== `[.contract-item-name]#++asset++#++(self: @ContractState) → ContractAddress++` [.item-kind]#external#

Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.

[.contract-item]
[[ERC4626Component-total_assets]]
==== `[.contract-item-name]#++total_assets++#++(self: @ContractState) → u256++` [.item-kind]#external#

Returns the total amount of the underlying asset that is “managed” by Vault.

[.contract-item]
[[ERC4626Component-convert_to_shares]]
==== `[.contract-item-name]#++convert_to_shares++#++(self: @ContractState, assets: u256) → u256++` [.item-kind]#external#

Returns the amount of shares that the Vault would exchange for the amount of assets provided,
in an ideal scenario where all the conditions are met.

[.contract-item]
[[ERC4626Component-convert_to_assets]]
==== `[.contract-item-name]#++convert_to_assets++#++(self: @ContractState, shares: u256) → u256++` [.item-kind]#external#

Returns the amount of assets that the Vault would exchange for the amount of shares provided,
in an ideal scenario where all the conditions are met.

[.contract-item]
[[ERC4626Component-max_deposit]]
==== `[.contract-item-name]#++max_deposit++#++(self: @ContractState, receiver: ContractAddress) → u256++` [.item-kind]#external#

Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,
through a deposit call.

If the `LimitConfigTrait` is not defined for deposits, returns 2 ** 256 - 1.

[.contract-item]
[[ERC4626Component-preview_deposit]]
==== `[.contract-item-name]#++preview_deposit++#++(self: @ContractState, assets: u256) → u256++` [.item-kind]#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.

If the `FeeConfigTrait` is not defined for deposits, returns the full amount of shares.

[.contract-item]
[[ERC4626Component-deposit]]
==== `[.contract-item-name]#++deposit++#++(ref self: ContractState, assets: u256, receiver: ContractAddress) → u256++` [.item-kind]#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 for `receiver`.

Emits a `Deposit` event.

[.contract-item]
[[ERC4626Component-max_mint]]
==== `[.contract-item-name]#++max_mint++#++(self: @ContractState, receiver: ContractAddress) → u256++` [.item-kind]#external#

Returns the maximum amount of the Vault shares that can be minted for `receiver` through a `mint` call.

If the `LimitConfigTrait` is not defined for mints, returns 2 ** 256 - 1.

[.contract-item]
[[ERC4626Component-preview_mint]]
==== `[.contract-item-name]#++preview_mint++#++(self: @ContractState, shares: u256) → u256++` [.item-kind]#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.

If the `FeeConfigTrait` is not defined for mints, returns the full amount of assets.

[.contract-item]
[[ERC4626Component-mint]]
==== `[.contract-item-name]#++mint++#++(self: @ContractState, shares: u256, receiver: ContractAddress) → u256++` [.item-kind]#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 for `receiver`.

Emits a `Deposit` event.

[.contract-item]
[[ERC4626Component-max_withdraw]]
==== `[.contract-item-name]#++max_withdraw++#++(self: @ContractState, owner: ContractAddress) → u256++` [.item-kind]#external#

Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the Vault,
through a `withdraw` call.

If the `LimitConfigTrait` is not defined for withdraws,
returns the full balance of assets for `owner` (converted to shares).

[.contract-item]
[[ERC4626Component-preview_withdraw]]
==== `[.contract-item-name]#++preview_withdraw++#++(self: @ContractState, assets: u256) → u256++` [.item-kind]#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.

If the `FeeConfigTrait` is not defined for withdraws, returns the full amount of shares.

[.contract-item]
[[ERC4626Component-withdraw]]
==== `[.contract-item-name]#++withdraw++#++(self: @ContractState, assets: u256, receiver: ContractAddress, owner: ContractAddress) → u256++` [.item-kind]#external#

Burns shares from `owner` and sends exactly `assets` of underlying tokens to `receiver`.

Requirements:

- `assets` is less than or equal to the max withdraw amount of `owner`.

Emits a `Withdraw` event.

[.contract-item]
[[ERC4626Component-max_redeem]]
==== `[.contract-item-name]#++max_redeem++#++(self: @ContractState, owner: ContractAddress) → u256++` [.item-kind]#external#

Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault,
through a `redeem` call.

If the `LimitConfigTrait` is not defined for redeems, returns the full balance of assets for `owner`.

[.contract-item]
[[ERC4626Component-preview_redeem]]
==== `[.contract-item-name]#++preview_redeem++#++(self: @ContractState, shares: u256) → u256++` [.item-kind]#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.

If the `FeeConfigTrait` is not defined for redeems, returns the full amount of assets.

[.contract-item]
[[ERC4626Component-redeem]]
==== `[.contract-item-name]#++redeem++#++(self: @ContractState, shares: u256, receiver: ContractAddress, owner: ContractAddress) → u256++` [.item-kind]#external#

Burns exactly `shares` from `owner` and sends assets of underlying tokens to `receiver`.

Requirements:

- `shares` is less than or equal to the max redeem amount of `owner`.

Emits a `Withdraw` event.

[.contract-item]
[[ERC4626Component-name]]
==== `[.contract-item-name]#++name++#++(self: @ContractState) → ByteArray++` [.item-kind]#external#

Returns the name of the token.

[.contract-item]
[[ERC4626Component-symbol]]
==== `[.contract-item-name]#++symbol++#++(self: @ContractState) → ByteArray++` [.item-kind]#external#

Returns the ticker symbol of the token, usually a shorter version of the name.

[.contract-item]
[[ERC4626Component-decimals]]
==== `[.contract-item-name]#++decimals++#++(self: @ContractState) → u8++` [.item-kind]#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

[.contract-item]
[[ERC4626Component-initializer]]
==== `[.contract-item-name]#++initializer++#++(ref self: ContractState, asset_address: ContractAddress)++` [.item-kind]#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.

[.contract-item]
[[ERC4626Component-_deposit]]
==== `[.contract-item-name]#++_deposit++#++(ref self: ContractState, caller: ContractAddress, receiver: ContractAddress, assets: u256, shares: u256)++` [.item-kind]#internal#

Business logic for <<ERC4626Component-deposit, deposit>> and <<ERC4626Component-mint, 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 the business logic.

Requirements:

- `ERC20::transfer_from` must return true.

Emits two `ERC20::Transfer` events (`ERC20::mint` and `ERC20::transfer_from`).

Emits a `Deposit` event.

[.contract-item]
[[ERC4626Component-_withdraw]]
==== `[.contract-item-name]#++_withdraw++#++(ref self: ContractState, caller: ContractAddress, receiver: ContractAddress, owner: ContractAddress, assets: u256, shares: u256)++` [.item-kind]#internal#

Business logic for <<ERC4626Component-withdraw, withdraw>> and <<ERC4626Component-redeem, 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 the business logic.

Requirements:

- `ERC20::transfer` must return true.

Emits two `ERC20::Transfer` events (`ERC20::burn` and `ERC20::transfer`).

Emits a `Withdraw` event.

[.contract-item]
[[ERC4626Component-_convert_to_shares]]
==== `[.contract-item-name]#++_convert_to_shares++#++(self: @ContractState, assets: u256, rounding: Rounding) -> u256++` [.item-kind]#internal#

Internal conversion function (from assets to shares) with support for `rounding` direction.

[.contract-item]
[[ERC4626Component-_convert_to_assets]]
==== `[.contract-item-name]#++_convert_to_assets++#++(self: @ContractState, shares: u256, rounding: Rounding) -> u256++` [.item-kind]#internal#

Internal conversion function (from shares to assets) with support for `rounding` direction.

== Presets

[.contract]
Expand Down Expand Up @@ -622,4 +939,4 @@ 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.
- `new_class_hash` cannot be zero.
8 changes: 8 additions & 0 deletions docs/modules/ROOT/pages/erc4626.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
= ERC4626

:eip20: https://eips.ethereum.org/EIPS/eip-20[EIP-20]
:eip4626: https://eips.ethereum.org/EIPS/eip-4626[EIP-4626]

{eip4626} is an extension of {eip20} that proposes a standard interface for token vaults.
This standard interface can be used by widely different contracts (including lending markets, aggregators, and intrinsically interest bearing tokens), which brings a number of subtleties.
Navigating these potential issues is essential to implementing a compliant and composable token vault.
1 change: 1 addition & 0 deletions packages/test_common/src/mocks.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod checkpoint;
pub mod erc1155;
pub mod erc20;
pub mod erc2981;
pub mod erc4626;
pub mod erc721;
pub mod governor;
pub mod multisig;
Expand Down
Loading
Loading