Skip to content
$1,000,000 in security audit grants are live now, Apply here →

Security review · April 2026

Staking Updates

for Magna

Guardian's review of Staking Updates for Magna, published April 2026. The report records 23 findings across 2 review rounds, including 3 low and 20 informational.

Published
Review window
April 8 to 15, 2026
Rounds
Main Review, Remediation Review
Language
Solidity
Chains
Ethereum, Base, Optimism, Polygon, Arbitrum, BNB Chain
Sector
Infrastructure
  • 0 Critical
  • 0 High
  • 0 Medium
  • 3 Low
  • 20 Informational

15 resolved · 1 partially resolved · 7 acknowledged

Scope

Findings 23

Main Review

18 findings · April 8 to 10, 2026
  1. L-01 Low Parameter Mismatch In Claim Event Events Resolved
    Location
    [https://github.com/magna-eng/protocol-staking-evm/blob/f46c88b4d6c480dd97e6259e9d21da1ae283c85d/src/dynamicApy/interfaces/IDynamicStaking.sol#L128](https://github.com/magna-eng/protocol-staking-evm/blob/f46c88b4d6c480dd97e6259e9d21da1ae283c85d/src/dynamicApy/interfaces/IDynamicStaking.sol#L128)
    Round
    Main Review

    Description

    IDynamicStaking.Claim names its third parameter penaltyAmount, implying that the value represents a penalty. However, when a user claims, the contract emits the actual reward amount: emit Claim(sender, stakeIdPart, actualClaimedAmount, claimFee).

    Off-chain indexers or dashboards that rely on ABI field names for semantics may misinterpret claimed rewards as penalties, leading to incorrect accounting or misleading alerts.

    Recommendation

    Rename the parameter and its associated comment in the interface (and any downstream consumers) to accurately reflect actual behavior.

  2. L-02 Low Multiple Config Values No Longer Observable Configuration Resolved
    Location
    [https://github.com/magna-eng/protocol-staking-evm/blob/f46c88b4d6c480dd97e6259e9d21da1ae283c85d/src/dynamicApy/DynamicStaking.sol#L38-L64](https://github.com/magna-eng/protocol-staking-evm/blob/f46c88b4d6c480dd97e6259e9d21da1ae283c85d/src/dynamicApy/DynamicStaking.sol#L38-L64)
    Round
    Main Review

    Description

    Key parameters such as minStakeAmount, stake time bounds, withdrawal delays, penalty caps, the whitelist toggle, and the fee manager were changed from public to internal without introducing replacement getter functions. Additionally, the previous helper getMinimumStakeAmount was removed in this PR.

    As a result, users and off-chain tools have no way to determine minimum stake amounts, lock periods, or whether proofless staking is allowed without directly inspecting raw storage.

    Recommendation

    Re-expose these parameters through public getters or dedicated view functions so that external systems retain visibility into the staking configuration.

  3. L-03 Low Precision loss due to integer division Math Resolved
    Location
    [https://github.com/magna-eng/protocol-staking-evm/blob/f46c88b4d6c480dd97e6259e9d21da1ae283c85d/src/dynamicApy/hooks/multiplier/DefaultCompositeMultiplierHook.sol#L52-L84](https://github.com/magna-eng/protocol-staking-evm/blob/f46c88b4d6c480dd97e6259e9d21da1ae283c85d/src/dynamicApy/hooks/multiplier/DefaultCompositeMultiplierHook.sol#L52-L84)
    Round
    Main Review

    Description

    DefaultCompositeMultiplierHook.getMultiplier composes four per-hook multipliers as (A * B / MULTIPLIER_SCALER) * C / MULTIPLIER_SCALER * D / MULTIPLIER_SCALER with MULTIPLIER_SCALER = 1e4. Each / MULTIPLIER_SCALER is integer division, so the result is floored three times in sequence. The mathematically exact value is (A × B × C × D) / MULTIPLIER_SCALER³ with a single rounding at the end; the current form drops fractional units after every intermediate step, so the composite multiplier is systematically ≤ the exact value. That biases virtual stake and reward share slightly down versus a single final division.

    Recommendation

    Compute in one shot after promoting to uint256, for example uint256(A) * B * C * D / (uint256(MULTIPLIER_SCALER) ** 3) then cast to uint32 after a bounds check. With each hook capped at 5 * MULTIPLIER_SCALER (Common.sol), the product fits in uint256 without overflow.

  4. I-01 Informational Warning Regarding multiExecute Warning Resolved
    Location
    [https://github.com/magna-eng/protocol-staking-evm/blob/f46c88b4d6c480dd97e6259e9d21da1ae283c85d/src/dynamicApy/BatchDeploy.sol#L34-L43](https://github.com/magna-eng/protocol-staking-evm/blob/f46c88b4d6c480dd97e6259e9d21da1ae283c85d/src/dynamicApy/BatchDeploy.sol#L34-L43)
    Round
    Main Review

    Description

    The multiExecute function is public and performs external calls to arbitrary addresses with arbitrary calldata. As a result, users can execute a wide range of external calls through the MultiExecute contract.

    This contract must never hold token balances and should not be granted approvals, as its entire token balance (excluding ETH) could be transferred and any approvals could be exploited via multiExecute.

    Recommendation

    Document this behavior and clearly warn users not to fund or approve the contract unless operations are executed atomically, as all funds held by the contract can be swept.

  5. I-02 Informational Typos Typo Partially resolved
    Location
    [https://github.com/magna-eng/protocol-staking-evm/blob/f46c88b4d6c480dd97e6259e9d21da1ae283c85d/src/dynamicApy/hooks/multiplier/NftMultiplierHook.sol#L11](https://github.com/magna-eng/protocol-staking-evm/blob/f46c88b4d6c480dd97e6259e9d21da1ae283c85d/src/dynamicApy/hooks/multiplier/NftMultiplierHook.sol#L11)
    Round
    Main Review

    Description

    A state variable in NftMultiplierHook contains a typo, defined as nftMulitplier instead of nftMultiplier.

    Additionally, the UnexpetedEthReceived, AlreadyTermintated, and AlreadyForecefullyTermintated errors contain typos.

    Recommendation

    Update the variable names and errors with typos and all functions that reference them.

  6. I-03 Informational Use Low Level Call Instead of Send Best Practices Resolved
    Location
    [https://github.com/magna-eng/protocol-staking-evm/blob/f46c88b4d6c480dd97e6259e9d21da1ae283c85d/src/dynamicApy/utils/WithDefundSupport.sol#L20](https://github.com/magna-eng/protocol-staking-evm/blob/f46c88b4d6c480dd97e6259e9d21da1ae283c85d/src/dynamicApy/utils/WithDefundSupport.sol#L20)
    Round
    Main Review

    Description

    The WithDefundSupport._defund function uses send to transfer native ETH. It is best practice to use the low-level call{value: amount}("") pattern instead, as send forwards only 2,300 gas

    Recommendation

    Consider using low-level call instead of send.

  7. I-04 Informational Multicall functionality offers limited benefits Best Practices Resolved
    Location
    BatchDeploy.sol
    Round
    Main Review

    Description

    The BatchDeploy contract contains two functions - deployContract() and multiExecute() - and it inherits from the Multicall contract. The Multicall.multicall() function lets users batch different function calls of the same contract by utilizing delegatecall. In the case of the current contract, users can call multiExecute() and deployContract() in the same call. However, multicall() is non-payable, so it only works for multiExecute() calls that don't perform native token payments.

    Functionally, there are no benefits of using multicall, because multiExecute() can be used to call deployContract() as well and there is no caller specific logic there. The only upside of using multicall is that it returns bytes[] return data, while multiExecute() ignores it.

    Recommendation

    Consider whether inheriting Multicall is necessary in this contract. If you decide to remove it, you can also modify multiExecute() to return the internal calls return data, just like currently multicall() does.

  8. I-05 Informational Same Salt Reverts Identical Batch Deploys DoS Resolved
    Location
    [https://github.com/magna-eng/protocol-staking-evm/blob/f46c88b4d6c480dd97e6259e9d21da1ae283c85d/src/dynamicApy/BatchDeploy.sol#L29](https://github.com/magna-eng/protocol-staking-evm/blob/f46c88b4d6c480dd97e6259e9d21da1ae283c85d/src/dynamicApy/BatchDeploy.sol#L29)
    Round
    Main Review

    Description

    deployContract loops over deploymentCodes and calls Create2.deploy(0, salt, deploymentCodes[i]) with the same salt for every index. CREATE2 resolves the contract address from deployer, salt, and the init code hash. If two entries share identical bytecode, they share the same init code hash, so the second deployment targets an address that already has code from the first iteration. OpenZeppelin’s Create2.deploy then reverts. Because the whole call runs in one transaction, the entire batch reverts.

    Recommendation

    Derive a per-entry salt so identical bytecode still maps to distinct addresses

  9. I-06 Informational Hook getters lack view Informational Resolved
    Location
    [https://github.com/magna-eng/protocol-staking-evm/blob/f46c88b4d6c480dd97e6259e9d21da1ae283c85d/src/dynamicApy/interfaces/IMultiplierHook.sol](https://github.com/magna-eng/protocol-staking-evm/blob/f46c88b4d6c480dd97e6259e9d21da1ae283c85d/src/dynamicApy/interfaces/IMultiplierHook.sol)
    Round
    Main Review

    Description

    IMultiplierHook.getMultiplier and IPenaltyHook.getPenaltyAmount are declared as external with returns (...) but without the view modifier.

    Recommendation

    Add view to both interface signatures so implementations must be read-only at compile time.

  10. I-07 Informational Missing events in state change functions Events Acknowledged
    Location
    Global
    Round
    Main Review

    Description

    The following functions change critical protocol state without emitting any event: modifyCampaign, setMerkleRoot, setEmergencyMode, setAllowStakingWithoutProof, setFeeParams, modifyMaximumRewardGenerationRate, modifyMinStakeAmount, withdrawAllPenaltyAmount, withdrawEthBalance, defundContractBalance (DynamicStaking.sol); setStakingContractAddress (PostWithdrawalHookBase.sol); setPenaltyRange (StakeTimeRangePenaltyHook.sol); setStakeAmountRange, setStakeTimeRange, setNftMultiplier (DefaultCompositeMultiplierHook.sol).

    Recommendation

    Add events to all state-changing admin functions

  11. I-08 Informational Penalty Hook / maxPenaltyPercentage Mismatch Best Practices Resolved
    Location
    [https://github.com/magna-eng/protocol-staking-evm/blob/f46c88b4d6c480dd97e6259e9d21da1ae283c85d/src/dynamicApy/DynamicStaking.sol#L196](https://github.com/magna-eng/protocol-staking-evm/blob/f46c88b4d6c480dd97e6259e9d21da1ae283c85d/src/dynamicApy/DynamicStaking.sol#L196)
    Round
    Main Review

    Description

    DynamicStaking.unstake() enforces two independent penalty checks:

    1. require(penaltyAmount == expectedPenaltyAmount) user slippage protection.
    2. require(penaltyAmount * PENALTY_SCALER * 100 / actualUnstaked <= maxPenaltyPercentage) immutable protocol cap set at deployment.

    These serve different purposes: the first protects the user against a rogue admin front-running the penalty hook; the second is a hard on-chain guarantee to all stakers that penalties will never exceed a fixed percentage regardless of hook configuration.

    However, maxPenaltyPercentage is immutable it cannot be changed after deployment. If it is set lower than the maximum penalty the hook can legitimately return (e.g. maxPenaltyPercentage = 10% but hook charges 50% for early exits), the second check will always revert for early unstakers. They cannot exit early under any circumstances and must wait until unstakableFrom when penalty is 0.

    Recommendation

    Document clearly that maxPenaltyPercentage must be >= the maximum penalty percentage the configured hook can ever return.

  12. I-09 Informational Sensitive roles self-administered, no admin veto Access Control Acknowledged
    Location
    DynamicStaking.sol
    Round
    Main Review

    Description

    setRole() calls _setRoleAdmin(role, role) for both FORCE_LIQUIDATE_POSITION_ROLE and DEFUND_CONTRACT_BALANCE_ROLE. Under OpenZeppelin AccessControl, only the role admin can grant or revoke that role, so ADMIN_ROLE cannot revoke a compromised holder. A compromised DEFUND_CONTRACT_BALANCE_ROLE holder can grant the role to additional addresses before anyone can react, then call defundContractBalance() and drain principal, rewards, penalties, and pending entries. There is no override path for ADMIN_ROLE once proliferation starts.

    Recommendation

    Use _setRoleAdmin(role, ADMIN_ROLE) for both roles so ADMIN_ROLE can revoke holders. For DEFUND_CONTRACT_BALANCE_ROLE especially, prefer a multi-sig rather than an EOA.

  13. I-10 Informational Warning For Users Regarding Arbitrary Hooks Warning Resolved
    Location
    [https://github.com/magna-eng/protocol-staking-evm/blob/f46c88b4d6c480dd97e6259e9d21da1ae283c85d/src/dynamicApy/DynamicStaking.sol#L113-L114](https://github.com/magna-eng/protocol-staking-evm/blob/f46c88b4d6c480dd97e6259e9d21da1ae283c85d/src/dynamicApy/DynamicStaking.sol#L113-L114)
    Round
    Main Review

    Description

    DynamicStaking accepts arbitrary multiplier, penalty, and post-withdrawal hook addresses in its constructor and does not enforce that these addresses point to the reference implementations provided in the repository. As a result, the deployer can supply any contracts they control.

    A malicious or buggy hook can reject transactions, confiscate tokens, or reroute withdrawals. Users must perform due diligence and trust the pool deployer, as well as the specific hook contracts selected. There is no on-chain registry of audited hooks, nor any restrictions preventing the deployer from setting arbitrary hooks.

    Recommendation

    Document this behavior clearly and warn users that deployers can freely choose hook contracts.

    If only the reference hooks in this repository are intended to be used, maintain a list of approved hook contracts and enforce on-chain restrictions during deployment.

  14. I-11 Informational Post-withdrawal hook treats amounts as same kind Compatibility Resolved
    Location
    [DynamicStaking.sol#L271-274](https://github.com/magna-eng/protocol-staking-evm/blob/f46c88b4d6c480dd97e6259e9d21da1ae283c85d/src/dynamicApy/DynamicStaking.sol#L271-L274)
    Round
    Main Review

    Description

    A comment in withdrawToRecipient suggests that a custom post-withdrawal hook can be used to differentiate between claims and unstakes:

    // withdrawToRecipient handles claims and stakes without any distinction, so if
    // for example claims should always use direct transfer and stakes should use a hook
    // then the solution is to disable direct transfer and create a special hook that can differentiate
    // between claims and stakes
    

    However, the hook interface does not provide enough information to achieve this. When a caller passes multiple entry types (e.g., [Unstake, Claim]), removePendingEntries processes both types sequentially and sums the results into a single actuallyRemovedAmount. The hook then receives:

    • amount: the aggregate withdrawn amount across all types
    • pendingEntryTypes: the array of types that were requested, not what was actually consumed

    There is no per-type amount breakdown passed to the hook. Given amount = 150 and pendingEntryTypes = [Unstake, Claim], the hook cannot determine whether 100 came from unstakes and 50 from claims, or any other split.

    A hook that needs to apply different logic to claims vs unstakes (e.g., different fee structures, routing, or access control) cannot function correctly for mixed withdrawals. The comment's suggested mitigation is incomplete — it relies on callers voluntarily making separate single-type calls, which cannot be enforced on-chain from the hook side.

    Recommendation

    If this is a desired feature, pass per-type amounts to the hook so it can apply type-specific logic without depending on caller cooperation. Otherwise, remove the comment.

  15. I-12 Informational Hook invoked on zero-amount withdrawals Validation Resolved
    Location
    [https://github.com/magna-eng/protocol-staking-evm/blob/f46c88b4d6c480dd97e6259e9d21da1ae283c85d/src/dynamicApy/DynamicStaking.sol#L285-L287](https://github.com/magna-eng/protocol-staking-evm/blob/f46c88b4d6c480dd97e6259e9d21da1ae283c85d/src/dynamicApy/DynamicStaking.sol#L285-L287)
    Round
    Main Review

    Description

    In DynamicStaking.withdrawToRecipient(), when isDirectTransfer = false and failOnZeroWithdrawal = false, the postWithdrawalHook.handlePostWithdrawal() is called unconditionally; even when actuallyRemovedAmount == 0 (for example no pending entries have matured yet).

    if (actuallyRemovedAmount != 0) {
        token.safeTransfer(address(postWithdrawalHook), actuallyRemovedAmount);
    }
    // called regardless of actuallyRemovedAmount
    postWithdrawalHook.handlePostWithdrawal(
        token, actuallyRemovedAmount, sender, recipient, stakeIdPart, pendingEntryTypes, extraData
    );
    

    Since integrators can deploy their own hook implementations extending PostWithdrawalHookBase, they must be aware that _handlePostWithdrawal can be invoked with amount = 0. A hook implementation that assumes a non-zero amount on every call will behave incorrectly in this case.

    Recommendation

    Add a prominent notice to PostWithdrawalHookBase and its documentation that _handlePostWithdrawal may be called with amount = 0. Integrators should always guard their logic with an early return or explicit check.

  16. I-13 Informational Nft multiplier can possibly be reused Informational Resolved
    Location
    [NftMultiplierHook.sol#L37](https://github.com/magna-eng/protocol-staking-evm/blob/f46c88b4d6c480dd97e6259e9d21da1ae283c85d/src/dynamicApy/hooks/multiplier/NftMultiplierHook.sol#L37)
    Round
    Main Review

    Description

    The NftMultiplierHook.getMultiplier() function returns the stored nftMulitplier if the staker owns the NFT. Depending on which collection this hook is used for, users can transfer the same NFT among each other in order to benefit from the stake multiplier.

    Recommendation

    This behavior of the hook must be taken into consideration when configuring a multiplier and a collection.

  17. I-14 Informational Hook owners can DOS users Trust Assumptions Resolved
    Location
    hooks
    Round
    Main Review

    Description

    There is a comment in DynamicStaking that highlights the possibility of a malicious hook owner DOS-ing user stakes by changing the multiplier.

            // each staking/unstaking(with penalty) in which case the admin can DoS a particular user.
            // This is not a major issue as users can always submit his transaction to a node with private pool.
            // If DoS turns out to be an issue in the future, then an update window feature can be implemented.
    

    The comment suggest that users can use private pools to avoid this, but in reality, the owner of the penalty hook can just set the penalty to an unreasonable value that the user wouldn't agree with, for example 100%, achieving the same DOS effect.

    The owner of the PostWithdrawalHookBase can call setStakingContractAddress() and change the staking address to an invalid address - this would DOS withdrawals for all users.

    Recommendation

    Make sure the users are aware of the risks related to hook owners.

  18. I-15 Informational Unnecessary pre-decrement Gas Optimization Resolved
    Location
    [StakeTimeRangePenaltyHook.sol#L89](https://github.com/magna-eng/protocol-staking-evm/blob/f46c88b4d6c480dd97e6259e9d21da1ae283c85d/src/dynamicApy/hooks/penalty/StakeTimeRangePenaltyHook.sol#L89)<br>[RangeMultiplierBase.sol#L20](https://github.com/magna-eng/protocol-staking-evm/blob/f46c88b4d6c480dd97e6259e9d21da1ae283c85d/src/dynamicApy/hooks/multiplier/bases/RangeMultiplierBase.sol#L20)
    Round
    Main Review

    Description

    StakeTimeRangePenaltyHook.getPenaltyPercentage() and RangeMultiplierBase.getRangeMultiplier() perform redundant --i on the last line, as the i variable is no longer used after that.

        function getPenaltyPercentage(uint32 value) internal view returns (uint32 penaltyPercentage) {
            uint256 rangeLength = penaltyRange.length;
    
            uint256 i = 1;
            while (i < rangeLength && penaltyRange[i].remainingTimePercentageLowerBound <= value) {
                ++i;
            }
            penaltyPercentage = penaltyRange[--i].penaltyPercentage;
        }
    
        function getRangeMultiplier(MultiplierRangeEntry[] storage range, uint256 value)
            internal
            view
            returns (uint32 multiplier)
        {
            uint256 rangeLength = range.length;
    
            uint256 i = 1;
            while (i < rangeLength && range[i].lowerBound <= value) {
                ++i;
            }
            multiplier = range[--i].multiplier;
        }
    

    Recommendation

    Consider using i - 1 instead.

    - penaltyPercentage = penaltyRange[--i].penaltyPercentage;
    + penaltyPercentage = penaltyRange[i - 1].penaltyPercentage;
    
    - multiplier = range[--i].multiplier;
    + multiplier = range[i - 1].multiplier;
    

Remediation Review

5 findings · April 15, 2026
  1. I-01 Informational Typos Typo Acknowledged
    Location
    Global
    Round
    Remediation Review

    Description

    Previously, there were 2 typos in the following errors:NotYetForecefullyTermintated (forEcefully and terminTated) and AlreadyForecefullyTermintated (forEcefully and terminTated). The latest commit fixed the E typo, but the T typo still remains.

    Additionally, the following typos exist in comments, NatSpec documentation, and one named return identifier:

    • DynamicStaking.sol#L532 — rouge should be rogue ("a small risk of a rouge admin").
    • DynamicStaking.sol#L548 — transfering should be transferring.
    • IDynamicStaking.sol#L62 — migth should be might, vaule should be value.
    • IDynamicStaking.sol#L143 — migth should be might, vaule should be value.
    • FixedStaking.sol#L320 — exectute should be execute.
    • FixedStaking.sol#L352 — rouge should be rogue.
    • FixedStaking.sol#L460 — preceeding should be preceding.
    • IFixedStaking.sol#L96 — named return compoundingPeriodLenght should be compoundingPeriodLength to match the function name.

    Recommendation

    Fix all of the typos. The compoundingPeriodLenght named return in IFixedStaking.sol is purely cosmetic (named returns in interfaces are not part of the ABI selector), but should be aligned with the function name compoundingPeriodLength for consistency.

  2. I-02 Informational Contract comparison emits compiler warning Warning Acknowledged
    Location
    [src/dynamicApy/utils/WithDefundSupport.sol:16](https://github.com/GuardianOrg/protocol-staking-evm-team1-1775663025703/blob/04559b636e2bf5ec1595557478bc2c4e702c67b9/src/dynamicApy/utils/WithDefundSupport.sol#L16)
    Round
    Remediation Review

    Description

    In WithDefundSupport._defund(), the native-token branch is selected via tokenParam == NATIVE_TOKEN. Since both operands are contract-typed values, Solidity emits a compiler warning for direct contract comparison and recommends comparing their addresses explicitly. This does not currently change runtime behavior, but it adds avoidable warning noise to builds and makes the sentinel-address intent less explicit than an address(...) comparison.

    Recommendation

    Compare the sentinel values through address(...) in WithDefundSupport._defund(), for example address(tokenParam) == address(NATIVE_TOKEN). This removes the compiler warning and makes the native-token check explicit and consistent with the surrounding zero-address validation.

  3. I-03 Informational Hook trust comment understates upgrade risk Documentation Acknowledged
    Location
    [src/dynamicApy/DynamicStaking.sol:187](https://github.com/GuardianOrg/protocol-staking-evm-team1-1775663025703/blob/04559b636e2bf5ec1595557478bc2c4e702c67b9/src/dynamicApy/DynamicStaking.sol#L187)
    Round
    Remediation Review

    Description

    The comment in DynamicStaking.unstake() says users only need to verify that the construction-time supplied penalty and multiplier hooks are not malicious as “a one-time check”. That guidance is only accurate if the referenced hooks are immutable and cannot be upgraded or materially reconfigured after deployment. If a hook is upgradeable, owner-controlled, or depends on mutable external state, its behavior can change over time and the trust assumption must be revisited continuously. Leaving the comment as-is may cause integrators and users to underestimate the ongoing trust and monitoring requirements of the configured hooks.

    Recommendation

    Update the comment to clarify that hook verification is only a one-time check when the hooks are immutable and non-upgradeable. If hooks are upgradeable or admin-configurable, document that users and integrators should treat them as ongoing trust assumptions and monitor them accordingly.

  4. I-04 Informational Unscoped salts blur deployment attribution Trust Assumptions Acknowledged
    Location
    [src/dynamicApy/MultiExecute.sol:33](https://github.com/GuardianOrg/protocol-staking-evm-team1-1775663025703/blob/04559b636e2bf5ec1595557478bc2c4e702c67b9/src/dynamicApy/MultiExecute.sol#L33)
    Round
    Remediation Review

    Description

    MultiExecute.deployContract() derives each CREATE2 salt as bytes32(uint256(salt) + i) without binding it to the caller. As a result, deterministic deployment addresses are namespaced only by the MultiExecute address, the derived salt, and the init code, not by the initiating user. Different users can therefore target the same deterministic deployment address by reusing the same salt and init code, or by choosing overlapping salt ranges across batches. This means callers cannot safely assume that a predictable address is uniquely tied to their own deployment flow or initiator identity. Any integrator or deployed contract logic that informally treats such an address as being attributable to a specific user, transaction origin, or deployment attempt is relying on a false trust assumption.

    Recommendation

    Scope the derived salt to the initiator in deployContract(), for example by hashing the caller together with the user-supplied salt and index. This makes deterministic deployment addresses user-specific and avoids collisions or attribution ambiguity across independent callers. If shared salts are intentional, document clearly that deterministic addresses produced by deployContract() are not bound to a particular caller.

  5. I-05 Informational Missing CREATE2 address computation getter Compatibility Acknowledged
    Location
    [src/dynamicApy/MultiExecute.sol:25](https://github.com/GuardianOrg/protocol-staking-evm-team1-1775663025703/blob/04559b636e2bf5ec1595557478bc2c4e702c67b9/src/dynamicApy/MultiExecute.sol#L25)
    Round
    Remediation Review

    Description

    MultiExecute exposes deployContract() for deterministic CREATE2 deployments but does not expose a view/helper function to compute the resulting deployment address from the supplied salt, batch index, and init code. While the address can be derived off-chain, the missing getter makes integration harder for contracts and on-chain workflows that need to reason about a future deployment address before it is created. This reduces composability, forces downstream users to reimplement the derivation logic externally, and increases the chance of mismatches if the derivation scheme is replicated incorrectly.

    Recommendation

    Add a view/helper function that returns the deployment address for a given salt, batch index, and deployment bytecode, and optionally a batch variant for multiple bytecodes. This would make deterministic deployments easier to consume from both off-chain tooling and on-chain integrations without requiring external reimplementation of the address derivation logic.

More from Magna

All 9 reports
  1. Airdrop Updates

    2 findings 2 findings: 1 low, 1 informational
  2. Direct Transfer

    9 findings 9 findings: 1 medium, 1 low, 7 informational
  3. Merkle Vester

    13 findings 13 findings: 1 medium, 6 low, 6 informational
  4. Fixed and Dynamic Staking

    21 findings1 high 21 findings: 1 high, 3 medium, 17 low

Put your code through the same review.

This review started with a conversation about scope. Tell us what you are building and we will plan yours with you.

Get a quote