-
Notifications
You must be signed in to change notification settings - Fork 352
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 #1170
base: main
Are you sure you want to change the base?
Add erc4626 #1170
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #1170 +/- ##
==========================================
- Coverage 92.26% 89.33% -2.94%
==========================================
Files 59 81 +22
Lines 1811 3470 +1659
==========================================
+ Hits 1671 3100 +1429
- Misses 140 370 +230
... and 78 files with indirect coverage changes Continue to review full report in Codecov by Sentry.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good job! Left a couple of comments
self.reenter_target.write(target); | ||
self.reenter_selector.write(selector); | ||
for elem in calldata { | ||
self.reenter_calldata.append().write(*elem); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we clear the calldata that already exists to allow for a repeated call?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't have to. There's only one reentrant call since we're setting the reentrant Type
to No
in the hooks after the initial call
/// Those methods should be performed separately. | ||
fn redeem( | ||
ref self: TState, shares: u256, receiver: ContractAddress, owner: ContractAddress | ||
) -> u256; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shall we include a newline between a function and doc for the next one?
Co-authored-by: immrsd <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good job! Left several suggestions
So, I have an implementation with most of the suggested changes in a separate branch (I can push the changes in this PR if preferred). The impl includes:
As mentioned off-line, I think this is too opinionated of an implementation. The suggested design (at least, my interpretation of it) makes it difficult to customize outside of standard entry and exit fees. A simple example where hooks would be very helpful is with implementing auto compounding rewards as seen here: https://github.com/ERC4626-Alliance/ERC4626-Contracts/blob/main/src/xERC4626.sol#L65-L74 Let me know if you guys disagree with my take or have ideas @ericnordelo @immrsd |
Co-authored-by: immrsd <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PR looks great @andrew-fleming, my only concern is that it should be as hard to misuse as possible, so I'm pushing for more clarity on the comments on some places, but besides that, it looks ready.
Let's continue with the current approach regarding hooks from the points you've shared.
/// Adjustments for fees expected to be defined at the contract level. | ||
/// Defaults to no entry or exit fees. | ||
/// To transfer fees, this trait needs to be coordinated with `ERC4626Component::ERC4626Hooks`. | ||
pub trait FeeConfigTrait<TContractState> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pub trait FeeConfigTrait<TContractState> { | |
pub trait FeeConfigTrait<TContractState, +HasComponent<TContractState>> { |
This is not strictly required, but is better semantically because it doesn't make much sense to implement this trait in a contract not implementing this component, and these guards against it.
Applies for the other traits too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done for fees, limits, and hooks! I know it's especially good to do it here, but it's probably worth going through the repo and seeing where else we can apply the component enforcement idea
} | ||
|
||
/// 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
which conditions?
} | ||
|
||
/// 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, from the comment is not clear what conditions need to be met.
assets | ||
} | ||
|
||
fn adjust_mint(self: @ComponentState<TContractState>, assets: u256) -> u256 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not clear at first glance what is the intention of these adjust functions or how to use them in the right way. We could add some explanation for that in comments for this trait or functions on how they must be used combined with fees for example, and if they have potentially other use cases the implementors (we) considered.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be as hard to misuse as possible.
/// The default deposit preview value is the full amount of shares. | ||
/// This can be changed in the implementing contract by defining custom logic in | ||
/// `FeeConfigTrait::adjust_deposit`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not clear why someone would want to change the deposit preview value, this comment can be confusing to users trying to understand the component. An explanation of how this can be useful and how to potentially use it (fees) would be very helpful.
Apply for the other preview functions mentioning the FeeConfigTrait trait.
Co-authored-by: Eric Nordelo <[email protected]>
WIP
Fixes #???
PR Checklist
...still a WIP. The tests need to be refactored and improved. The idea was to make sure the logic works as expected so there's a standard (from the openzeppelin solidity tests) for any and all changes moving forward.
Some things to note and discuss:
Decimals
EIP4626 states at the end of the doc:
The OpenZeppelin solidity implementation checks for the underlying asset's tokens upon construction with a try/catch which defaults to
18
decimals if query fails. Given Starknet's current status with try/catch (✌️ dual dispatchers), this doesn't seem like a viable approach. If the vault is for an undeployed token, the deployment will fail. This PR proposes that the decimals (both underlying decimals and offset decimals) are explicitly defined by the contract through the ImmutableConfig.Math
u512 Precision math for multiply and divide (
u256_mul_div
)This PR leverages the corelib's
wide_mul
andu512_safe_div_rem_by_u256
for mathematical precision. This is set as a tmp solution and should be scrutinized further. More tests should be provided and we should look for ways to optimize.Exponentiation
This PR requires exponentiation for converting to shares and assets. The current implementation is the brute force formula. We can definitely improve this.This was added to the corelib (starkware-libs/cairo#6694). Will update when released
FeeConfigTrait
This PR proposes to utilize
FeeConfigTrait
(which is really like a hook) for contracts to integrate entry and exit fees forpreview_
fns. The state-changing methods of ERC4626 rely onpreview_
to determine the number of assets or shares to exchange.Another approach that can reduce the verbosity of the traits/hooks is to have a single
adjust_assets_or_shares
function that accepts anExchangeType
.The downside though is I think it's easy to misuse i.e.
IMO having a dedicated function for each exchange type is more difficult to mess up...but it's at the cost of some verbosity.
LimitsConfigTrait
This mirrors the
FeeConfigTrait
except that these target the limits on themax_
methods and return anOption
soOption::None
can point to the default. Same arguments apply for not having a single trait/hook with anExchangeType
parameter.before_withdraw
andafter_deposit
hooksThe
before_withdraw
andafter_deposit
hooks take inspiration from solmate's solidity implementation of erc4626. These hooks are where contracts can transfer fees calculated from theFeeConfigTrait
in the implementing contract. See the Fees mock to see how this works in the proposed PR.