Skip to content

Commit

Permalink
Add erc4626 (#1170)
Browse files Browse the repository at this point in the history
* start erc4626

* add fns from interface

* start business logic

* add mul_div and tests

* simplify math

* add fix me comments

* fix fmt

* fix fmt

* add convert_to logic, add metadata impl

* reexports

* add erc4626 mock

* add erc4626 mock

* start erc4626 tests

* comment out mods and tests to improve performance

* add offset config in mock

* add overflow assertion and test

* add power fn

* add erc20reentrant mock

* fix interface fns

* fix logic, add power

* add starting tests-no assets, no shares

* fix fmt

* clean up power fn

* simplify operation

* add comments, fix visibility

* move fn, remove tests

* add test_math mod

* add mint to mock vault construction

* add full vault tests

* fix fmt

* fix assertions

* add full vault redeem tests

* fix fmt

* fix fmt

* fix fmt

* move erc4626 tests to erc4626 dir

* add transfer assertion

* add reentrancy tests

* expose burn in mock

* tidy up tests

* add default decimals mock

* fix deploy fn names

* improve helper fn name

* fix fmt

* add comments, remove unused dep

* update branch

* fix conflicts

* fix fmt

* update spdx

* add reqs to mul_div, fix spdx

* remove duplicate erc4626 in votes

* add/fix in-code docs

* fix param name

* fix comments

* add multiple tx tests

* fix err msgs

* fix comments, fix test

* add note to power

* use math:: prefix in tests

* improve u256_mul_div

* remove unused var

* remove line

* improve var name

* add changelog entries

* fix version in spdx

* Apply suggestions from code review

Co-authored-by: Eric Nordelo <[email protected]>

* use corelib pow, remove power fn

* add zero addr test

* fix max and preview comments

* Apply suggestions from code review

Co-authored-by: immrsd <[email protected]>

* improve interface fmt

* re-enable cairo-coverage in CI

* tmp lower codecov coverage

* tmp increase threshold

* Apply suggestions from code review

Co-authored-by: immrsd <[email protected]>

* fix comment

* add LimitConfig comments

* Apply suggestions from code review

Co-authored-by: Eric Nordelo <[email protected]>

* add HasComponent to traits

* fix fmt

* fix comments

* fix interface comments

* tidy up mock

* fix fmt

* add fees comment to mock

* improve comments

* add comments to hook fns

* fix fmt

* improve FeeConfigTrait NOTE

* fix fmt

* Apply suggestions from code review

Co-authored-by: Eric Nordelo <[email protected]>

* fix fmt

* remove unused vars

* Apply suggestions from code review

Co-authored-by: immrsd <[email protected]>

* improve docs

* improve docs

* return max_limit or assets_bal, whichever is less

* improve docs

* improve mock

* fix fmt

* improve limit docs

* fix fmt

* improve comments

* Apply suggestions from code review

Co-authored-by: Eric Nordelo <[email protected]>

* Update packages/token/src/erc20/extensions/erc4626/erc4626.cairo

Co-authored-by: immrsd <[email protected]>

* remove trailing space

* fix fmt

* fix comments

---------

Co-authored-by: Eric Nordelo <[email protected]>
Co-authored-by: immrsd <[email protected]>
  • Loading branch information
3 people authored Feb 5, 2025
1 parent 6dfe58c commit b2059b0
Show file tree
Hide file tree
Showing 17 changed files with 3,452 additions and 26 deletions.
24 changes: 10 additions & 14 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,8 @@ jobs:
with:
starknet-foundry-version: ${{ env.FOUNDRY_VERSION }}

# Issue with cairo-coverage. Re-add to CI once issues are fixed.
#
# - name: Install cairo-coverage
# run: curl -L https://raw.githubusercontent.com/software-mansion/cairo-coverage/main/scripts/install.sh | sh
- name: Install cairo-coverage
run: curl -L https://raw.githubusercontent.com/software-mansion/cairo-coverage/main/scripts/install.sh | sh

- name: Markdown lint
uses: DavidAnson/markdownlint-cli2-action@05f32210e84442804257b2a6f20b273450ec8265 # v16
Expand All @@ -52,13 +50,11 @@ jobs:
- name: Run tests
run: snforge test --workspace --features fuzzing --fuzzer-runs 500

# Issue with cairo-coverage. Re-add to CI once issues are fixed.
#
# - name: Run tests and generate coverage report
# run: snforge test --workspace --coverage
#
# - name: Upload coverage to Codecov
# uses: codecov/codecov-action@v4
# with:
# file: ./coverage.lcov
# token: ${{ secrets.CODECOV_TOKEN }}
- name: Run tests and generate coverage report
run: snforge test --workspace --coverage

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
file: ./coverage.lcov
token: ${{ secrets.CODECOV_TOKEN }}
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,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
12 changes: 7 additions & 5 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,22 @@ comment:
coverage:
# The value range where you want the value to be green
# Hold ourselves to a high bar.
range: 90..100
# TMP 80 floor until cairo-coverage becomes more stable
range: 80..100
status:
project:
coverage:
# Use the coverage from the base commit (pull request base) coverage to compare against.
# Once we have a baseline we can be more strict.
# TMP threshold until cairo-coverage becomes more stable
target: auto
threshold: 2%
threshold: 4%
patch:
default:
# Require new code to have 90%+ coverage.
target: 90%
threshold: 2%
# TMP target and threshold until cairo-coverage becomes more stable
target: 80%
threshold: 4%

ignore:
- "**/tests/**"
Expand All @@ -27,4 +30,3 @@ ignore:

github_checks:
annotations: false

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
162 changes: 162 additions & 0 deletions packages/test_common/src/mocks/erc20.cairo
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use starknet::ContractAddress;

#[starknet::contract]
pub mod DualCaseERC20Mock {
use openzeppelin_token::erc20::{DefaultConfig, ERC20Component, ERC20HooksEmptyImpl};
Expand Down Expand Up @@ -224,3 +226,163 @@ pub mod DualCaseERC20PermitMock {
self.erc20.mint(recipient, initial_supply);
}
}

#[derive(Drop, Serde, PartialEq, Debug, starknet::Store)]
pub enum Type {
#[default]
No,
Before,
After,
}

#[starknet::interface]
pub trait IERC20ReentrantHelpers<TState> {
fn schedule_reenter(
ref self: TState,
when: Type,
target: ContractAddress,
selector: felt252,
calldata: Span<felt252>,
);
fn function_call(ref self: TState);
fn unsafe_mint(ref self: TState, recipient: ContractAddress, amount: u256);
fn unsafe_burn(ref self: TState, account: ContractAddress, amount: u256);
}

#[starknet::interface]
pub trait IERC20Reentrant<TState> {
fn schedule_reenter(
ref self: TState,
when: Type,
target: ContractAddress,
selector: felt252,
calldata: Span<felt252>,
);
fn function_call(ref self: TState);
fn unsafe_mint(ref self: TState, recipient: ContractAddress, amount: u256);
fn unsafe_burn(ref self: TState, account: ContractAddress, amount: u256);

// IERC20
fn total_supply(self: @TState) -> u256;
fn balance_of(self: @TState, account: ContractAddress) -> u256;
fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256;
fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool;
fn transfer_from(
ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256,
) -> bool;
fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool;
}

#[starknet::contract]
pub mod ERC20ReentrantMock {
use openzeppelin_token::erc20::ERC20Component;
use starknet::ContractAddress;
use starknet::SyscallResultTrait;
use starknet::storage::{MutableVecTrait, Vec};
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
use starknet::syscalls::call_contract_syscall;
use super::Type;

component!(path: ERC20Component, storage: erc20, event: ERC20Event);

#[abi(embed_v0)]
impl ERC20Impl = ERC20Component::ERC20Impl<ContractState>;
#[abi(embed_v0)]
impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl<ContractState>;
#[abi(embed_v0)]
impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl<ContractState>;
impl InternalImpl = ERC20Component::InternalImpl<ContractState>;

#[storage]
pub struct Storage {
#[substorage(v0)]
pub erc20: ERC20Component::Storage,
reenter_type: Type,
reenter_target: ContractAddress,
reenter_selector: felt252,
reenter_calldata: Vec<felt252>,
}

#[event]
#[derive(Drop, starknet::Event)]
enum Event {
#[flat]
ERC20Event: ERC20Component::Event,
}

//
// Hooks
//

impl ERC20ReentrantImpl of ERC20Component::ERC20HooksTrait<ContractState> {
fn before_update(
ref self: ERC20Component::ComponentState<ContractState>,
from: ContractAddress,
recipient: ContractAddress,
amount: u256,
) {
let mut contract_state = self.get_contract_mut();

if contract_state.reenter_type.read() == Type::Before {
contract_state.reenter_type.write(Type::No);
contract_state.function_call();
}
}

fn after_update(
ref self: ERC20Component::ComponentState<ContractState>,
from: ContractAddress,
recipient: ContractAddress,
amount: u256,
) {
let mut contract_state = self.get_contract_mut();

if contract_state.reenter_type.read() == Type::After {
contract_state.reenter_type.write(Type::No);
contract_state.function_call();
}
}
}

#[abi(embed_v0)]
pub impl ERC20ReentrantHelpers of super::IERC20ReentrantHelpers<ContractState> {
fn schedule_reenter(
ref self: ContractState,
when: Type,
target: ContractAddress,
selector: felt252,
calldata: Span<felt252>,
) {
self.reenter_type.write(when);
self.reenter_target.write(target);
self.reenter_selector.write(selector);
for elem in calldata {
self.reenter_calldata.append().write(*elem);
}
}

fn function_call(ref self: ContractState) {
let target = self.reenter_target.read();
let selector = self.reenter_selector.read();
let mut calldata = array![];
for i in 0..self.reenter_calldata.len() {
calldata.append(self.reenter_calldata.at(i).read());
};
call_contract_syscall(target, selector, calldata.span()).unwrap_syscall();
}

fn unsafe_mint(ref self: ContractState, recipient: ContractAddress, amount: u256) {
self.erc20.mint(recipient, amount);
}

fn unsafe_burn(ref self: ContractState, account: ContractAddress, amount: u256) {
self.erc20.burn(account, amount);
}
}

#[constructor]
fn constructor(ref self: ContractState, name: ByteArray, symbol: ByteArray) {
self.erc20.initializer(name, symbol);
self.reenter_type.write(Type::No);
}
}
Loading

0 comments on commit b2059b0

Please sign in to comment.