Part 2

Zaros
PerpetualsDEXFoundrySolidity
70,000 USDC
View results
Submission Details
Severity: low
Valid

`initiateSwap` allows users to initiate swap even when the vault is paused

Summary

The initiateSwap function in StabilityBranch.sol lacks proper validation of vault status before accepting user's USD tokens. When users attempt to swap tokens using a paused vault ID, their tokens get transferred and locked in the contract while the subsequent fulfillSwap operation by the keeper will revert due to vault.live() checks.

Vulnerability Details

From the Vault.sol code, we can see there are three different loading functions:

  • load(): Basic loading without checks

  • loadExisting(): Checks if vault exists

  • loadLive(): Checks if vault exists AND is active

// In Vault.sol
function load(uint128 vaultId) internal pure returns (Data storage vault)
function loadExisting(uint128 vaultId) internal view returns (Data storage vault) {
if (vault.id == 0) {
revert Errors.VaultDoesNotExist(vaultId);
}
}
function loadLive(uint128 vaultId) internal view returns (Data storage vault) {
vault = loadExisting(vaultId);
if (!vault.isLive) {
revert Errors.VaultIsDisabled(vaultId);
}
}

The initiateSwap function uses Vault.load() which doesn't validate whether the vault is live or not:
StabilityBranch.sol#L241

function initiateSwap(
uint128[] calldata vaultIds,
uint128[] calldata amountsIn,
uint128[] calldata minAmountsOut
)
external
{
// Perform length checks
if (vaultIds.length != amountsIn.length) {
revert Errors.ArrayLengthMismatch(vaultIds.length, amountsIn.length);
}
if (amountsIn.length != minAmountsOut.length) {
revert Errors.ArrayLengthMismatch(amountsIn.length, minAmountsOut.length);
}
// working data
InitiateSwapContext memory ctx;
// cache the vault's index token and asset addresses
@> Vault.Data storage currentVault = Vault.load(vaultIds[0]);
// ....
for (uint256 i; i < amountsIn.length; i++) {
// for all but first iteration, refresh the vault and enforce same collateral asset
if (i != 0) {
@> currentVault = Vault.load(vaultIds[i]);
// ...

When a vault gets paused by protocol administrators, users can still call initiateSwap for this paused vault.

More importantly when keeper calls fulfillSwap, it will revert bcoz it checks the vault is paused or not :
StabilityBranch.sol#L360

Vault.Data storage vault = Vault.loadLive(ctx.vaultId);

Example Scenario:

  1. A vault gets paused by protocol administrators

  2. Users can still call initiateSwap for this paused vault

  3. Their tokens get transferred to the contract

  4. The keeper's fulfillSwap operation will inevitably fail due to Vault.loadLive() check

Impact

Users tokens can get temporarily locked in the contract if the vault is paused.

Tools Used

Manual review

Recommendations

Replace Vault.load(vaultIds[i]) with Vault.loadLive(vaultIds[i]) in the initiateSwap function to enforce vault status validation at the entry point. This would prevent users from initiating swaps with paused vaults:

- Vault.Data storage vault = Vault.load(vaultIds[i]);
+ Vault.Data storage vault = Vault.loadLive(vaultIds[i]);
Updates

Lead Judging Commences

inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Validated
Assigned finding tags:

`initiateSwap` allows users to initiate swap even when the vault is paused

Appeal created

0xshoonya Submitter
10 months ago
inallhonesty Lead Judge
10 months ago
inallhonesty Lead Judge 9 months ago
Submission Judgement Published
Validated
Assigned finding tags:

`initiateSwap` allows users to initiate swap even when the vault is paused

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!