Skip to content

Conversation

@0x-r4bbit
Copy link
Member

This commit introduces a maximum limit of vaults an account can own in the staking system. This is to fix the situation that accounts can create a large amount of vaults to avoid getting slashed.

To enable vault limits, the following notable changes were made:

  • StakeManager.MAX_VAULTS_PER_USER() is a constant that contains the limit
  • StakeManager.registerVault() now when trying to register more than MAX_VAULTS_PER_USER vaults
  • StakeManager.migrateTo(address) now deregisters the vault that the account is migrating from
  • StakeVault.replaceVault() has been introduced to allow for migrating to a new vault instance if the max vault limit is already reached
  • To make StakeVault.replaceVault() possible StakeManager.createMigrationVault() was introduced, which bypasses the max vault limit temporarily, to replace an older vault
  • Respectively, VaultFactory.createMigrationVault() has been introduced as well (we could've gotten away with just this addition, but since we can't StakeVault's if the vault factory changes, they have to make them go through StakeManager instead, which ultimately calls the vault factory)
  • StakeManager.leave() also deregisters the vault that is leaving. This prevents accounts from locking themselves out of the system by creating max limit vaults and have them all leave.
  • Tests have been added accordingly

BREAKING CHANGES:

  • StakeManager.registerVault() now only allows for StakeManager.MAX_VAULTS_PER_USER() vaults per user and reverts otherwise
  • StakeManager.migrateTo(address) now deregisters the vault that calls this function
  • StakeManager.leave() also deregisters the vault that calls this function

Addresses https://github.com/Cyfrin/audit-2025-12-statusl2/issues/11

Closes #73

CONSTANTS
//////////////////////////////////////////////////////////////////////////*/

uint8 public constant MAX_VAULTS_PER_USER = 5;
Copy link
Member Author

Choose a reason for hiding this comment

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

@gravityblast @3esmit What should the max be? I went with 5 as it's more than enough I think.
Also, shall we call this MAX_VAULTS_PER_ACCOUNT instead/

Copy link
Member

Choose a reason for hiding this comment

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

yes I think MAX_VAULTS_PER_ACCOUNT makes sense.

for the number, can't we have it variable so an admin can change it?

it feels too random now to take a decision on a number like that

IStakeVault(migrateTo).migrateFromVault(migrationData);

delete vaultData[msg.sender];
_deregisterVault(msg.sender);
Copy link
Member Author

Choose a reason for hiding this comment

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

Now that we have limited amount of vaults, deregistering is actually feasible, which allows us to clean up after ourselves in migrateToVault() and leave()

vaults[vaultOwner].push(address(vault));
emit VaultRegistered(address(vault), vaultOwner);
return vault;
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Notice that this function is only called by StakeVault.replaceVault().
As mentioned in the commit, StakeVault should actually be calling VaultFactory for this, but since there's a chance we'll replace the factory in the future, we need StakeVault to do this via StakeManager instead.

Key insights here are:

  • createMigrationVault() really just creates a vault via VaultFactory, without the max limit check
  • It also does the registerVault() logic. I've inlined it here, because otherwise the VaultFactory would do registerVault(), which is a bit of an unnecessary back in forth (also registerVault() has the max limit check, which we otherwise need to make conditional)

address migrateTo = address(stakeManager.createMigrationVault());
_migrateToVault(migrateTo);
return migrateTo;
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Notice that this largely leverages functionality that was already there.
We just create the migration vault in a slightly different way.

*/
function createMigrationVault(address vaultOwner) external onlyStakeManager returns (StakeVault clone) {
return _createVault(vaultOwner);
}
Copy link
Member Author

Choose a reason for hiding this comment

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

createMigrationVautl() really just means createVaultWithoutRegistration()
Happy to rename this @gravityblast @3esmit

@0x-r4bbit 0x-r4bbit force-pushed the fix/vault-max-limits branch 3 times, most recently from 412247d to 6b0c7d6 Compare December 9, 2025 20:24
@0x-r4bbit 0x-r4bbit changed the base branch from fix/vault-owner-bug to develop December 11, 2025 12:50
@0x-r4bbit 0x-r4bbit force-pushed the fix/vault-max-limits branch from 6b0c7d6 to 4355c78 Compare December 11, 2025 14:11
This commit introduces a maximum limit of vaults an account can own
in the staking system. This is to fix the situation that accounts can create
a large amount of vaults to avoid getting slashed.

To enable vault limits, the following notable changes were made:

- `StakeManager.maxVaultsPerUser()` is a configurable field to set the max amount of vaults per user
- `StakeManager.registerVault()` now when trying to register more than `maxVaultsPerUser` vaults
- `StakeManager.migrateTo(address)` now deregisters the vault that the account is migrating from
- `StakeVault.replaceVault()` has been introduced to allow for migrating to a new vault instance if the max vault limit
  is already reached
- To make `StakeVault.replaceVault()` possible `StakeManager.createMigrationVault()` was introduced, which bypasses the
  max vault limit temporarily, to replace an older vault
- Respectively, `VaultFactory.createVaultWithoutRegistration()` has been introduced as well (we could've gotten away with just
  this addition, but since we can't `StakeVault`'s if the vault factory changes, they have to make them go through
  `StakeManager` instead, which ultimately calls the vault factory)
- `StakeManager.leave()` also deregisters the vault that is leaving. This prevents accounts from locking themselves out
  of the system by creating max limit vaults and have them all leave.
- Tests have been added accordingly

BREAKING CHANGES:

- `StakeManager.registerVault()` now only allows for `StakeManager.maxVaultsPerUser()` vaults per user
  and reverts otherwise
- `StakeManager.migrateTo(address)` now deregisters the vault that calls this function
- `StakeManager.leave()` also deregisters the vault that calls this function

Addresses Cyfrin/audit-2025-12-statusl2#11
Closes #73
@0x-r4bbit 0x-r4bbit force-pushed the fix/vault-max-limits branch from 4355c78 to e8e89e3 Compare December 12, 2025 14:54
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.

2 participants