-
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.
Hey @andrew-fleming , the overall implementation looks great! I left a couple of comments, but my main suggestion is to simplify the fee mechanism.
With the current approach I feel it would be hard for users to follow how to implement fees, and is easy to misuse since adjust_[operation]
functions doesn't have a percent let it clear on which percentages are used, or on how to transfer the fees on the Hooks accordingly from the amounts received as parameters.
I think we can do something like this to simplify the logic and make it easier to follow and use:
- Have a configurable FEE_DENOMINATOR in ImmutableConfig for precision default to 10000 (100 is 1%)
- Have only two functions in the FeeConfigTrait,
entry_fee_numerator
andexit_fee_numerator
. Entry used for both deposit and mint, and exit for redeem and withdraw. - Remove the Hooks trait and add the transfer fee logic in
_deposit
and_withdraw
, with an if depending on the fee being greater than 0. - Compute the fee and apply it where it needs to be applied (preview functions).
- Maybe add a couple of internal functions like
entry_fee_set
andexit_fee_set
checking that the numerators are greater than 0.
I know this will add a couple of conditions and operations to the flow when the fees are not required (set to 0), but I think it will be negligible, and it will improve the UX a lot making it easier for users to implement a fee mechanism.
|
||
/// 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. |
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 implementation of LimitConfigTrait must always be available, even if it is the default. We should rephrase this. I don't make any specific suggestions since you will write a better sentence for sure.
|
||
/// 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. |
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 as above.
Co-authored-by: Eric Nordelo <eric.nordelo39@gmail.com>
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 <103599616+immrsd@users.noreply.github.com>
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 <103599616+immrsd@users.noreply.github.com>
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 <eric.nordelo39@gmail.com>
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.