Quantstamp reviewed Fragmetric Restaking Program on 1 Nov 2024: 17 findings, 0 critical; 4 marked fixed, 12 acknowledged, 1 open.
Report hosted on certificate.quantstamp.com.
Trustblock does not review code.
Statuses are the security firm’s own, from its report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 0 | 0 | 2 |
| Medium | 1 | 1 | 1 |
| Low | 0 | 11 | 1 |
The function `check_withdrawal_enabled()` checks for the boolean flag `withdrawal_enabled_flag` that can be set by the admin. The flag determines if users are allowed to call the `request_withdrawal()` function. However, the flag is also checked during the final call to `withdraw()`, when users can withdraw the amount they requested after the batch is processed. This means that if the flag is set to true, users will not be able to call the function `withdraw()` even if they have an already processed amount they can claim.
The admin has the ability to set a withdrawal fee rate. This value is stored in basis points, which means that a rate of `1` is equal to a 0.01% fee. There are no restrictions on the value the admin can set. This includes the ability to set withdrawal rates above 100% which would block withdrawals. In order to make users feel more secure and prevent the admin from disabling withdrawals via the rate, it is recommended to enforce a maximum allowed rate.
1. In `user_fund_supported_token_context.rs`, function `deposit_supported_token()`, when emitting the event `UserDepositedSupportedTokenToFund`, it should be clarified if `fund_account` should be created with the values of `receipt_token_price` and `receipt_token_total_supply` before or after the deposit is recorded. 2. In `user_fund_context.rs`, function `deposit_sol()`, when emitting the event `UserDepositedSOLToFund`, it should be clarified if `fund_account` should be created with the values of `receipt_token_price` and `receipt_token_total_supply` before or after the deposit is recorded. 3. In `user_receipt_token_transfer_context.rs`, function `handle_transfer()`, when emitting the `UserTransferredReceiptToken`, it should be clarified if `source_fund_account` and `destination_fund_account` should use placeholders created with the address of the funds, or with the address of the underlying users. 4. In `operator_fund_context.rs`, function `process_fund_withdrawal_job()`, when emitting the event `OperatorProcessedJob`, it should be clarified if `fund_account` should be created with the values of `receipt_token_total_supply` before or after the withdrawal is recorded.
In `user_fund_context.rs`and the function `withdraw()`, if accounting discrepancies occur, no check prevents withdrawing all lamports from `fund_account`, which would result in the deletion of the account's data.
The protocol implements a withdrawal flow, where a user has to first request a withdrawal, through the `request_withdrawal()`, and then it has to be processed along with other withdrawal requests, if any. To process withdrawal requests, the operator needs to either wait for a pre-determined amount of time between each batch (`batch_processing_threshold_duration`), or a threshold amount (`batch_processing_threshold_amount`). This means that a user with a high stake, that is greater than `batch_processing_threshold_amount` can deposit and withdraw instantly.
In all but one case, the global `RewardPool` is always updated alongside the `UserRewardPool`. During deposits and withdrawals for instance. However, there is one case where a `UserRewardPool` will be updated without updating the `RewardPool` it is part of, namely this is the `update_user_reward_pools()` function that is callable by any user. There are no specific issues that are related to this case, but it might cause issues down the line if some future functionality expects the `UserRewardPool` and `RewardPool` to be updated together.
**Description** It is important to validate inputs, even if they only come from trusted addresses, to avoid human error: 1. In `update.rs`, it is possible to call `add_holder()` with: a) an empty vector `pubKeys`; b) a vector `pubKeys` containing duplicates; 2. In `fund_manager_fund_supported_token_context.rs`, we can have in `FundManagerFundSupportedTokenContext` the following equality: `receipt_token_mint == supported_token_mint`. 3. In `user_fund_supported_token_context.rs`, function `deposit_supported_token()` and in `user_fund_context.rs`, function `deposit_sol()`, the values of `metadata` (`wallet_provider` and `contribution_accrual_rate`) are not sanitized. 4. In `fund_manager_fund_context.rs`, we can use: a) in `update_sol_withdrawal_fee_rate()` any value for `sol_withdrawal_fee_rate`; b) in `update_batch_processing_threshold()` any value for `amount` and `duration`; 5. It is possible to execute the instruction `fund_manager_settle_reward()` for an `amount` of `0`, which can create a `RewardSettlementBlock` of `amount == 0`.
The protocol allows the deposit of a set of supported assets, or simply `SOL`, in exchange for `fragSol`. When users want to withdraw, they will get an amount of `SOL` that is equivalent to the `fragSol` value at the time of withdrawal. However, **there is no functionality for the admin to withdraw the supported tokens deposited by the users**. The protocol will not allow withdrawal at its first stage, which mitigates the risk that users might withdraw all the `SOL` deposited and leave the other supported tokens.
The functions `init_without_load()` and `bump()` access the value of `data` at a given `offset`. However, no check makes sure that `offset` is not an out-of-bound ID.
The closure `is_contribution_accrual_rate_valid` currently checks if the rate is invalid (i.e., not in the range `100..200`), so the closure name is misleading.
The protocol tracks `fragSol` transfers using a custom transfer hook. Currently, the feature is disabled, and transfers are not allowed. However, the transfer hook does not perform an allocation update for the source and the destination. This means if transfers were to be enabled, specifically in the function `UserReceiptTokenTransferContext.handle_transfer()` the balances and rewards of the source and destination will remain the same.
An underflow error can happen without explicit error if a user executes a transaction with `user_deposit_sol()` or `user_deposit_supported_token()` as the first instruction with `metadata`. In this case, the variable `current_ix_index` in the function `verify_preceding_ed25519_instruction()` will take the value `0`, and underflow will happen at the line: ``` let ix = instructions::load_instruction_at_checked(current_ix_index - 1, instructions_sysvar)?; ```
The report names a repository or files. Open the report to see its scope.
Repository and files the report names (1)
236 projects reviewed · 332 reports · latest Oct 2026
See Quantstamp’s auditsSomething wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.
Tell us