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

Update last total assets in _setCap #338

Merged
merged 12 commits into from
Dec 28, 2023
Merged

Update last total assets in _setCap #338

merged 12 commits into from
Dec 28, 2023

Conversation

Jean-Grimal
Copy link
Contributor

test/forge/MarketTest.sol Outdated Show resolved Hide resolved
Co-authored-by: Merlin Egalite <[email protected]>
Signed-off-by: Jean-Grimal <[email protected]>
Rubilmax
Rubilmax previously approved these changes Dec 11, 2023
@MerlinEgalite MerlinEgalite requested review from a team, adhusson, QGarchery and MathisGD and removed request for a team December 11, 2023 11:23
Copy link
Collaborator

@adhusson adhusson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the lastTotalAssets = lastTotalAssets + supply(vault)update suspicious. It looks like the start of a loop that inflates lastTotalAssets. There is also no update of lastTotalAssets when supplyCap == 0.

Suggestion: unconditionally call _updateLastTotalAssets(totalAssets()) at the end of every _setCap. Unless _setCap is called very often I think it's worth the gas cost.

In that case the assert in testEnableMarketWithLiquidity should become:

uint depositedWithInterest = morpho.expectedSupplyAssets(allMarkets[0],address(vault));
assertEq(vault.lastTotalAssets(), depositedWithInterest + additionalSupply);

@Jean-Grimal
Copy link
Contributor Author

Suggestion: unconditionally call _updateLastTotalAssets(totalAssets()) at the end of every _setCap. Unless _setCap is called very often I think it's worth the gas cost.

I first used _updateLastTotalAssets(totalAssets()) as you can see here, but the gas savings are worth it imo.

In that case the assert in testEnableMarketWithLiquidity should become:

uint depositedWithInterest = morpho.expectedSupplyAssets(allMarkets[0],address(vault));
assertEq(vault.lastTotalAssets(), depositedWithInterest + additionalSupply);

I think assertEq(vault.lastTotalAssets(), depositedWithInterest + additionalSupply); and assertEq(vault.lastTotalAssets(), deposited + additionalSupply); are equivalent

@MerlinEgalite
Copy link
Contributor

MerlinEgalite commented Dec 11, 2023

There is also no update of lastTotalAssets when supplyCap == 0.

Was there a reason not to do that @morpho-org/onchain-reviewers? I can't remember

@Rubilmax
Copy link
Contributor

I find the lastTotalAssets = lastTotalAssets + supply(vault) update suspicious. It looks like the start of a loop that inflates lastTotalAssets. There is also no update of lastTotalAssets when supplyCap == 0.

Suggestion: unconditionally call _updateLastTotalAssets(totalAssets()) at the end of every _setCap. Unless _setCap is called very often I think it's worth the gas cost.

Do you mean it could open up a vulnerability?
Because at the same time, it looks totally correct to me: increasing the cap of a market adds enables this market (if it wasn't enabled before), thus adds it to the withdraw queue and the assets supplied in this market will be counted in the total assets, but should not be taxed (hence why we increase lastTotalAssets)

There is also no update of lastTotalAssets when supplyCap == 0.

Was there a reason not to do that @morpho-org/onchain-reviewers? I can't remember

Because even if a cap is zero, the vault has access to this supply hence why the total assets are not decreased (and why lastTotalAssets shouldn't be decreased either)

@adhusson
Copy link
Collaborator

The idea was that lastTotalAssets could be artificially inflated by alternating calls to 1) setCap on a market, with a nonzero cap, and 2)updateWithdrawQueue such that the vault removes the market but maintains nonzero assets in the market. Then calling lastTotalAssets = lastTotalAssets + morpho.supply(market,vault) would have double-counted the assets owned by the vault in the market.

I couldn't see a way to make the loop work without timelocks during which lastTotalAssets could be reset, and anyway #337 will prevent the "vault is still counting its assets in a removed market" situation. So I think it's fine.

MerlinEgalite
MerlinEgalite previously approved these changes Dec 13, 2023
@MathisGD MathisGD dismissed stale reviews from MerlinEgalite and Rubilmax via e06f7f0 December 26, 2023 21:22
Copy link
Contributor

@MathisGD MathisGD left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not super convinced about this issue. Why shouldn't a donation be taxed by the fee? And for me a removed market is lost funds, thus it's weird to modify the logic for this edge case.

@QGarchery
Copy link
Contributor

I'm not super convinced about this issue. Why shouldn't a donation be taxed by the fee? And for me a removed market is lost funds, thus it's weird to modify the logic for this edge case.

I believe this comment also answers this question

QGarchery
QGarchery previously approved these changes Dec 27, 2023
MerlinEgalite
MerlinEgalite previously approved these changes Dec 27, 2023
Rubilmax
Rubilmax previously approved these changes Dec 27, 2023
Copy link
Contributor

@Rubilmax Rubilmax left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the end I'm fine taxing twice temporarily lost funds, and I'm also fine to prevent taxing it because the cost is not much

@MathisGD
Copy link
Contributor

I still find it weird to merge this PR without having #337

MathisGD
MathisGD previously approved these changes Dec 28, 2023
@MathisGD MathisGD merged commit b5578f2 into post-cantina Dec 28, 2023
6 checks passed
@MathisGD MathisGD deleted the fix/issue-63 branch December 28, 2023 14:26
@Jean-Grimal Jean-Grimal mentioned this pull request Dec 28, 2023
@@ -737,6 +744,8 @@ contract MetaMorpho is ERC4626, ERC20Permit, Ownable2Step, Multicall, IMetaMorph

marketConfig.enabled = true;

_updateLastTotalAssets(lastTotalAssets + MORPHO.expectedSupplyAssets(marketParams, address(this)));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider avoiding executing this function if MORPHO.expectedSupplyAssets(marketParams, address(this)) is equal to zero.

I would say that the normal scenario should be that you are enabling a market where no one has supplied on behalf of MM.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, but it would save only ~2k gas since we still need to call expectedSupplyAssets. In the current configuration we're adding one cold SLOAD and one warm SSTORE that is not changing the value. So no sure it's worth considering this case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure it's worth it considering this case. The current implementation only adds one cold SLOAD and one warm SSTORE so around 2k gas.

@@ -737,6 +744,8 @@ contract MetaMorpho is ERC4626, ERC20Permit, Ownable2Step, Multicall, IMetaMorph

marketConfig.enabled = true;

_updateLastTotalAssets(lastTotalAssets + MORPHO.expectedSupplyAssets(marketParams, address(this)));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If not documented yet, consider documenting the side effects of this operation. The share value (of MM) is increased.

@@ -737,6 +744,8 @@ contract MetaMorpho is ERC4626, ERC20Permit, Ownable2Step, Multicall, IMetaMorph

marketConfig.enabled = true;

_updateLastTotalAssets(lastTotalAssets + MORPHO.expectedSupplyAssets(marketParams, address(this)));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should there be a check that reverts if the market is broken?

MORPHO.expectedSupplyAssets won't revert if elapsed == 0 (and the IRM is broken) and it does not use the oracle (called once you need to withdraw)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's ok not to revert in this case. Vaults curators should not list broken markets in the first place.

@StErMi
Copy link

StErMi commented Dec 30, 2023

Just to recap: this feature allows you to add the existing supply of a new (supplyCap > 0 && market.enabled == false) market to the lastTotalAssets state variable without taxing it (the increase is not seen as interest accrual that needs to be taxed). The result is that share value is increased without taxing it.

The scenarios where this is needed are:

  • donated funds
  • re-adding a previously removed market

This does not handle the case that suppliers that were present at removal time could end up with less value in their shares if new suppliers have minted new shares in the meanwhile (before the market is re-added)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants