feat(4626 wrapper): implements a 4626 wrapping of debt tokens#86
feat(4626 wrapper): implements a 4626 wrapping of debt tokens#860xMcsweeja wants to merge 17 commits intomainnetfrom
Conversation
|
Largely looks fine to me with some caveats - what we're probably going to want here is a primary factory that any market can deploy a wrapper contract for once and once only (so tracking centrally via the factory itself whether a wrapper contract exists), so we can extend the UI with a 'wanna wrap? here. wrapper doesn't exist yet? deploy it yourself!' To the extent that access control is needed, I'd leave it pretty open ended in the sense that anyone can hit the factory to create a new instance of a vault wrapper if one doesn't exist, since its' existence doesn't affect the market in any form. For the second question, for the sake of sanity I'd also include a sweep function for arbitrary ERC20s, with the account permitted to do that being the owner of the market itself. Wouldn't necessarily call it 'owner' for the wrapper since the only thing it could do is sweep, but I guess that's six and two threes. |
|
Naturally my thoughts on 'LGTM' are to be overridden by @d1ll0n |
|
Why are we using different math for preview vs execution? deposits, mints, burns, all use rayDiv and rayMul, which does half-up rounding the same as the market. convertToShares/Assets uses mulDiv which floors, and mulDivUp which is ceiling. i dont think it's a problem per se, but don't understand why we don't use the _scaledX functions for the previews, so we match the market ray math. |
kethcode
left a comment
There was a problem hiding this comment.
I dont see any reason to hold this up, so I'm going to approval it.
I've already mentioned the preview math not matching the execution math, but realistically that not material.
There are some gas optimizations we could consider. Wrapper cap is fied to uint256.max. I don't know if we ever want to allow the borrower to change that. If we don't plan to change that, we could not spend on the store sot and SLOAD... just inline type(unint256).max from maxDeposit/maxMint.
We should fix the SPDX declaration.
For cross-chain visibility and convenience, we could consider assetsPerShareRay() view helpers or something, so convertToAssets doesn't need to choose a sample share size.
We already have wrapperForMarket. If we want to do marketForWrapper, we could consider a view function or an event, but we ealready expse market(), so not really necesssary.
A few nits thrown by aderyn, but outside the scope here, not worth holding things up over.
Good work.
|
Updates from reviews / feedback: Feedback from @d1ll0n via tg:
feedback from @kethcode :
other stuff:
|
feat(validation): ensure wrapped market is a wildcat market
src/vault/Wildcat4626Wrapper.sol
Outdated
| return MathUtils.mulDiv(assets, RAY, scaleFactor); | ||
| } | ||
|
|
||
| function _convertToSharesUp(uint256 assets, uint256 scaleFactor) internal pure returns (uint256) { |
There was a problem hiding this comment.
I'm confused why this exists. we are operating on a wrapped market. we already have _scaledSharesForAssets and _scaledAssetsForShares. why not do something like
function convertToShares(uint256 assets) public view override returns (uint256) {
if (assets == 0) return 0;
uint256 scaleFactor = wrappedMarket.scaleFactor();
return _scaledSharesForAssets(assets, scaleFactor);
}
function convertToAssets(uint256 shares) public view override returns (uint256) {
if (shares == 0) return 0;
uint256 scaleFactor = wrappedMarket.scaleFactor();
return _scaledAssetsForShares(shares, scaleFactor);
}
this should match the existing market behavior, and also matches how you implemented maxWithdraw and previewWithdraw.
edit:
ok, i see the conflict. our market does not behave like EIP-4626 requires. convertToShares/convertToAssets and previewDeposit/previewRedeem must round down; previewMint/previewWithdraw must round up. Our market token converts between assets and scaled balances with half-up rounding (via rayDiv/rayMul). We should decide if the preview/runtime mismatch specified by EIP-4626 is preferred, or havin the preview match the market behavior. the difference is at most 1 unit in a uint256, so EIP-4626 alignment may be preferred, in which case your code is correct.
|
updates: sanctions support:
rounding:
tests:
open questions / comments:
|
|
I have some questions about sanctions and nukeFromOrbit(). it looks like the borrower can override a sanction on false positives, but does that create a race condition with nuke? whomever gets to it first wins? We may want offchain monitoring of sanction state of markets so borrowers can be notified and can call overrideSanction(wrapper) before they get nuked. market.maxTotalSupply - wrapper.totalAssets has already been brought up a couple times. donations could be used for griefing i guess. in theory, could totalAssets exceed the cap, locking deposit/mint? big enough donation disabling new wraps. the totalAssets thing might cause mispricing if other protocols ignore convertToAssets/assetsPerShareRay. mulDiv and MulDivUp revert on overflow as desired. not a problem as long as we are operating within realistic inputs. previews of type(uint256).max would revert for instance, but not really an issue. We should explicitly put cap details and totalAssets behavrio in the documentation we share with integrators. |
Signed-off-by: Dave Coleman <dave@wildcat.finance>
Signed-off-by: Dave Coleman <dave@wildcat.finance>
Signed-off-by: Dave Coleman <dave@wildcat.finance>
Signed-off-by: Dave Coleman <dave@wildcat.finance>
Signed-off-by: Dave Coleman <dave@wildcat.finance>
Signed-off-by: Dave Coleman <dave@wildcat.finance>
|
This has been released to the auditor. |
…ue 2 and 3 will be acknowledged but ignored. Signed-off-by: Dave Coleman <dave@wildcat.finance>

GH Issue
Factory wrapper for bridging debt tokens
TLDR
wraps a wildcat market debt token and exposes a non-rebasing share supply. Shares represent the underlying scaled balance of the wrapper inside the market; interest accrual is reflected through the share price rather than token balances.
entering vault:
exiting vault:
random notes:
nameandsymbolbased on feedback from L (that i may have misinterpreted)Clanker Feature Summary:
non‑rebasing ERC20 that track the market’s scaled balances (i.e., share count you can bridge cross‑chain).
symmetric exact‑shares path.
totalAssets but don’t dilute shares (assets become stranded). Borrower can sweep non‑market ERC20s only.
WildcatMarket.