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

Security review · August 2026

PSM Adapter

for Ethena

Guardian's review of PSM Adapter for Ethena, published August 2026. The report records 8 findings across 2 review rounds, including 1 low and 7 informational.

Published
Review window
August 5 to 10, 2026
Rounds
Main Review, Remediation Review
Language
Solidity
Chains
Ethereum
Sector
Stablecoins
  • 0 Critical
  • 0 High
  • 0 Medium
  • 1 Low
  • 7 Informational

6 resolved · 2 acknowledged

Findings 8

Main Review

7 findings · August 5 to 7, 2026
  1. L-01 Low Stale sell liquidity view Unexpected Behavior Acknowledged
    Location
    src/basin/IPSMAdapter.sol:109-110
    Round
    Main Review

    Description

    The adapter integration documents that Securitize must update ExternalAssetProvider.availableAsset() to delegate to EthenaPSMAdapter.availableAsset(), but there is no equivalent sell-side liquidity view for ExternalLiquidityProvider.availableLiquidity(). In the Securitize ELP implementation, availableLiquidity() resolves the liquidity custodian through getLiquidityCustodian()/_custodianOf() and, when the external provider is EthenaPSMAdapter, this reports USDC.balanceOf(address(adapter)).

    That value does not represent redeem-side executable liquidity. Sell output is sourced by PSM.swap() from the PSM collateral send custodian and is bounded by custodian balance, custodian allowance, and sell-direction PSM caps. As a result, availableLiquidity() can underreport liquidity when the adapter holds no USDC, or overreport liquidity when USDC fees temporarily sit on the adapter before sweep().

    This does not directly block redemptions because ExternalLiquidityProvider.supplyExactIn() does not use availableLiquidity() as an execution precheck, but frontends or off-chain integrators using the public off-ramp liquidity view may display or act on stale liquidity data.

    Recommendation

    Consider adding a sell-direction liquidity view to EthenaPSMAdapter that accounts for collateral send custodian balance/allowance and swapForCollateral PSM caps, and have Securitize delegate ExternalLiquidityProvider.availableLiquidity() to it. Alternatively, explicitly document that availableLiquidity() is not meaningful for this adapter integration and should not be used for off-ramp capacity decisions.

  2. I-01 Informational Adapter uses generic low amount error Informational Acknowledged
    Location
    https://github.com/GuardianOrg/onchain-minting-internal-team1-1785887214892/blob/d015d3b0a3a2bf401135bf9267a07c44602e5597/src/basin/EthenaPSMAdapter.sol#L165, https://github.com/GuardianOrg/onchain-minting-internal-team1-1785887214892/blob/d015d3b0a3a2bf401135bf9267a07c44602e5597/src/basin/EthenaPSMAdapter.sol#L195
    Round
    Main Review

    Description

    In swapExactIn(), the adapter checks amountIn < PSM_MIN_AMOUNT_IN before resolving the swap direction with _resolveDirection(). As a result, both collateral-to-asset and asset-to-collateral swaps revert with the same AmountInBelowPsmMinimum error, instead of preserving the PSM’s direction-specific distinction between InvalidCollateralAmount and InvalidAssetAmount.

    Recommendation

    Consider whether the adapter should keep the current generic AmountInBelowPsmMinimum error for simplicity, or introduce direction-specific low-amount errors.

  3. I-02 Informational Underdocumented same beneficiary reason Documentation Resolved
    Location
    README.md
    Round
    Main Review

    Description

    The README documents the following key invariant.

    Key invariant: the adapter always sets beneficiary: address(this) so it can measure the balance delta (received == amountOut) before forwarding funds to the ramp.

    While this is technically true, there is another reason why beneficiary must be set to the adapter and it is because the benefactor of the order is the adapter as well.

    PSM._validateBenefactor() reverts if the benefactor and the beneficiary differ, unless the beneficiary is approved for the given benefactor.

            if (order.benefactor != order.beneficiary && !_benefactorState.config.approvedBeneficiaries[order.beneficiary])
            {
                revert BeneficiaryNotApproved(order.beneficiary);
            }
    

    Because the adapter has no way to call setApprovedBeneficiary(), this check would have always reverted if the two didn't match.

    Recommendation

    Add this requirement to the key invariant documentation.

  4. I-03 Informational Adapter lacks per-investor authorization Informational Resolved
    Location
    src/basin/EthenaPSMAdapter.sol:234-235
    Round
    Main Review

    Description

    EthenaPSMAdapter.swapExactIn() does not identify or authorize the underlying investor. On the Securitize public on-ramp path, investor access is gated by the BUIDL/Securitize registry and token compliance checks rather than a ramp-specific allowlist. Therefore, any token-level eligible investor can buy or sell through the configured ramp path. The downstream PSM order sets both benefactor and beneficiary to address(this), so all ramp users consume the same adapter-level benefactor caps rather than separate per-investor caps.

    Recommendation

    Consider documenting this trust boundary explicitly. If shared token-level investor eligibility is intended, no code change is required. If Ethena wants granular per-investor or per-ramp access control, add authorization or limits in the Securitize ramp/provider path.

  5. I-04 Informational availableAsset() may show dust capacity Informational Resolved
    Location
    src/basin/EthenaPSMAdapter.sol:303
    Round
    Main Review

    Description

    EthenaPSMAdapter.availableAsset() returns the remaining BUIDL capacity without considering PSM_MIN_AMOUNT_IN. If the amount of BUIDL can be received by paying USDC below the floor, availableAsset() will return that BUIDL amount, even though it cannot be executed.

    This inconsistency affects only view paths, since EAP.supplyExactIn() calls previewSwapExactIn() before availableAsset(), and previewSwapExactIn() reverts if the provided amount is below the floor.

    Recommendation

    If this is fine, acknowledge the finding. If not, return 0 in case the available variable is below the minimum executable amount.

      uint256 minExecutableOut = _pegAmountOut(uint128(PSM_MIN_AMOUNT_IN), true);
      if (available < minExecutableOut) return 0;
    
  6. I-05 Informational PSM rotation can move pending fees Informational Resolved
    Location
    https://github.com/GuardianOrg/onchain-minting-internal-team1-1785887214892/blob/d015d3b0a3a2bf401135bf9267a07c44602e5597/src/basin/EthenaPSMAdapter.sol#L313-L319, https://github.com/GuardianOrg/onchain-minting-internal-team1-1785887214892/blob/d015d3b0a3a2bf401135bf9267a07c44602e5597/src/basin/EthenaPSMAdapter.sol#L356-L366
    Round
    Main Review

    Description

    EthenaPSMAdapter.setPsm() rotates the configured PSM without first sweeping token balances held by the adapter. EthenaPSMAdapter.sweep() always reads the current PSM and sends pending USDC/BUIDL balances to the current PSM custodian addresses.

    As a result, if USDC ramp fees or stray BUIDL are present on the adapter when setPsm() is called, a later sweep() sends those pre-rotation balances to the new PSM custodians rather than the custodians configured on the previous PSM.

    Recommendation

    Document that operators should call sweep() before setPsm() when balances should be attributed to the previous PSM custodian set.

  7. I-06 Informational Incomplete IGroveBasinLike documentation Documentation Resolved
    Location
    https://github.com/GuardianOrg/onchain-minting-internal-team1-1785887214892/blob/d015d3b0a3a2bf401135bf9267a07c44602e5597/src/basin/IGroveBasinLike.sol#L85-L89, https://github.com/GuardianOrg/onchain-minting-internal-team1-1785887214892/blob/d015d3b0a3a2bf401135bf9267a07c44602e5597/src/basin/IGroveBasinLike.sol#L101
    Round
    Main Review

    Description

    The previewSwapExactIn() documentation states that the preview may overestimate when the PSM oracle price deviates below pegPrice. This only describes the buy-direction case.

    Since previewSwapExactIn() replicates only the peg path while swapExactIn() uses psm.getQuote() and returns the lower of the peg path and oracle path, overestimation for sells can happen whenever the oracle path is lower than the peg path. Whether this occurs below or above pegPrice depends on direction: below pegPrice for buys, and above pegPrice for sells.

    Additionally, the previewSwapExactIn and swapExactIn() documentations refers to tokenIn and tokenOut while the actual parameters are named assetIn and assetOut.

    Recommendation

    Consider updating the previewSwapExactIn() documentation to state that overestimation can occur whenever the oracle path wins, with the side of peg depending on swap direction. Also replace tokenIn/tokenOut with assetIn/assetOut.

Remediation Review

1 finding · August 10, 2026
  1. I-01 Informational README misstates minimum input units Documentation Resolved
    Location
    src/basin/README.md:75-76
    Round
    Remediation Review

    Description

    The README states that amounts below PSM_MIN_AMOUNT_IN revert in collateral-token units. However, EthenaPSMAdapter.swapExactIn() applies PSM_MIN_AMOUNT_IN to amountIn before resolving the swap direction, so the floor is denominated in the input token’s units. For sells, this means the threshold is in asset decimals, not collateral decimals.

    Recommendation

    Update the README to state that PSM_MIN_AMOUNT_IN is enforced in input-token units before direction resolution.

More from Ethena

All 7 reports
  1. Ethena Pay Updates

    81 findings3 high 81 findings: 3 high, 14 medium, 39 low, 25 informational
  2. Onchain Minting

    21 findings 21 findings: 3 medium, 9 low, 9 informational
  3. Ethena Pay

    34 findings 34 findings: 3 medium, 7 low, 24 informational
  4. Execution Guard

    12 findings 12 findings: 2 medium, 4 low, 6 informational

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