Skip to content
This repository has been archived by the owner on Oct 1, 2023. It is now read-only.

berndartmueller - Adversary can trigger a regular end epoch for a null epoch and cause premium vault users to lose funds #414

Closed
sherlock-admin opened this issue Mar 27, 2023 · 0 comments
Labels
Duplicate A valid issue that is a duplicate of an issue with `Has Duplicates` label Medium A valid Medium severity issue Reward A payout will be made for this issue

Comments

@sherlock-admin
Copy link
Contributor

sherlock-admin commented Mar 27, 2023

berndartmueller

high

Adversary can trigger a regular end epoch for a null epoch and cause premium vault users to lose funds

Summary

Premium vault depositors lose their original deposits if the collateral vault has zero TVL and the ControllerPeggedAssetV2.triggerEndEpoch function is incorrectly called for a null epoch.

Vulnerability Detail

An epoch can be triggered invalid (i.e., null epoch) if one of the vaults has zero TVL by calling the ControllerPeggedAssetV2.triggerNullEpoch function. The counterparty vault, which has a non-zero TVL, will get the deposits refunded, and users can withdraw their original deposits.

However, even though an epoch qualifies as a null epoch, the ControllerPeggedAssetV2.triggerEndEpoch function can still be called. Incorrectly triggering the regular epoch's end will set the claimable TVL for the premium vault to 0 (line 181) and the premiums will be sent to the collateral vault.

If the premium vault has non-zero TVL and the collateral vault has zero TVL, premium vault depositors lose their original deposits.

Impact

An adversary can wrongfully call the triggerEndEpoch function for a null epoch and cause the premium vault depositors not to receive their deposits back.

Code Snippet

v2/Controllers/ControllerPeggedAssetV2.sol - triggerEndEpoch()

144: function triggerEndEpoch(uint256 _marketId, uint256 _epochId) public {
145:     address[2] memory vaults = vaultFactory.getVaults(_marketId);
146:
147:     if (vaults[0] == address(0) || vaults[1] == address(0))
148:         revert MarketDoesNotExist(_marketId);
149:
150:     IVaultV2 premiumVault = IVaultV2(vaults[0]);
151:     IVaultV2 collateralVault = IVaultV2(vaults[1]);
152:
153:     if (
154:         premiumVault.epochExists(_epochId) == false ||
155:         collateralVault.epochExists(_epochId) == false
156:     ) revert EpochNotExist();
157:
158:     (, uint40 epochEnd, ) = premiumVault.getEpochConfig(_epochId);
159:
160:     if (block.timestamp <= uint256(epochEnd)) revert EpochNotExpired();
161:
162:     //require this function cannot be called twice in the same epoch for the same vault
163:     if (premiumVault.epochResolved(_epochId)) revert EpochFinishedAlready();
164:     if (collateralVault.epochResolved(_epochId))
165:         revert EpochFinishedAlready();
166:
167:     premiumVault.resolveEpoch(_epochId);
168:     collateralVault.resolveEpoch(_epochId);
169:
170:     uint256 epochFee = vaultFactory.getEpochFee(_epochId);
171:
172:     uint256 premiumTVL = premiumVault.finalTVL(_epochId);
173:     uint256 collateralTVL = collateralVault.finalTVL(_epochId);
174:
175:     uint256 premiumFee = calculateWithdrawalFeeValue(premiumTVL, epochFee);
176:
177:     uint256 premiumTVLAfterFee = premiumTVL - premiumFee;
178:     uint256 collateralTVLAfterFee = collateralTVL + premiumTVLAfterFee;
179:
180:     // strike price is not reached so premium is entiled to 0
181: @>  premiumVault.setClaimTVL(_epochId, 0); // @audit-info premium vault's claim TVL is set to 0
182:     // strike price is not reached so collateral is entitled to collateralTVL + premiumTVLAfterFee
183:     collateralVault.setClaimTVL(_epochId, collateralTVLAfterFee);
184:
185:     // send premium fees to treasury and remaining TVL to collateral vault
186:     premiumVault.sendTokens(_epochId, premiumFee, treasury);
187:     // strike price reached so collateral is entitled to collateralTVLAfterFee
188:     premiumVault.sendTokens(
189:         _epochId,
190:         premiumTVLAfterFee,
191:         address(collateralVault)
192:     );
193:
194:     emit EpochResolved(
195:         _epochId,
196:         _marketId,
197:         VaultTVL(collateralTVLAfterFee, collateralTVL, 0, premiumTVL),
198:         false,
199:         block.timestamp,
200:         0
201:     );
202: }

Tool used

Manual Review

Recommendation

Consider adding checks to the triggerEndEpoch function to ensure that it is not called for null epochs (same as it is accomplished in the triggerDepeg function in lines 81-86).

Duplicate of #108

@github-actions github-actions bot closed this as completed Apr 3, 2023
@github-actions github-actions bot added Medium A valid Medium severity issue Duplicate A valid issue that is a duplicate of an issue with `Has Duplicates` label labels Apr 3, 2023
@sherlock-admin sherlock-admin added the Reward A payout will be made for this issue label Apr 11, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Duplicate A valid issue that is a duplicate of an issue with `Has Duplicates` label Medium A valid Medium severity issue Reward A payout will be made for this issue
Projects
None yet
Development

No branches or pull requests

1 participant