Farseen0/scar-pairs huggingface dataset:
HIGH severity: Hard-coded 165-byte vault account is too small for Raydium Token-2022 mints with extensions. **Description:** `stake_clmm_position` creates the vault token account for the raydium position mint with a size 165 Bytes

The constant **165 bytes** is the legacy SPL-Token size.
Raydium CLMM position NFTs are **Token-2022** mints that append several on-chain extensions (e.g., `MetadataPointer`,.....

Vulnerable code:
// Create vault token account if it doesn't exist
        let vault_account_info = ctx.accounts.vault_position_token_account.to_account_info();
        if vault_account_info.data_is_empty() {
            // Create the vault token account
            let vault_account_space = 165; // Standard token account size
            let vault_account_lamports = ctx.accounts.rent.minimum_balance(vault_account_space);

            let farm_key = ctx.accounts.farm.key();
            let position_mint_key = ctx.accounts.position_mint.key();
            let vault_seeds: &[&[u8]] = &[
                VAULT_SEED,
                farm_key.as_ref(),
                position_mint_key.as_ref(),
                &[ctx.bumps.vault_position_token_account],
            ];
            let vault_signer_seeds = &[vault_seeds];

            // Create account
            anchor_lang::system_program::create_account(
                CpiContext::new_with_signer(
                    ctx.accounts.system_program.to_account_info(),
                    anchor_lang::system_program::CreateAccount {
                        from: ctx.accounts.user.to_account_info(),
                        to: vault_account_info.clone(),
                    },
                    vault_signer_seeds,
                ),
                vault_account_lamports,
                vault_account_space as u64,
                &ctx.accounts.token_program.key(),
            )?;

            // Initialize the token account
            anchor_spl::token_interface::initialize_account3(
                CpiContext::new(
                    ctx.accounts.token_program.to_account_info(),
                    InitializeAccount3 {
                        account: vault_account_info.clone(),
                        mint: ctx.accounts.position_mint.to_account_info(),
                        authority: ctx.accounts.vault_authority.to_account_info(),
                    },
                ),
            )?;
        }
LOW severity: Hard-coded `9` decimals for xBELO mint can mismatch underlying BELO mint. **Description:** During `initialize` the program creates the xBELO receipt token like this:

The number of decimals is fixed at **9** instead of copying `belo_mint.decimals`.
If the canonical BELO mint was deployed with any other precision (e.g., 6 or 18), the 1 : 1 accounting assumption between...

Vulnerable code:
#[account(
    init, payer = admin,
    mint::decimals = 9,            // ← hard-coded
    mint::authority = state,
    mint::freeze_authority = state
)]
pub xgkhan_mint: Account<'info, Mint>,

#[account(
          init, payer=admin,
-         mint::decimals=9,
+         mint::decimals=gkhan_mint.decimals,
          mint::authority=state,
          mint::freeze_authority=state
      )]
      pub xgkhan_mint: Account<'info, Mint>,
LOW severity: Improper Rounding in Burn Logic Lets Users Avoid Penalty. **Description:** Vesting can happen with any positive value, it doesn't have a minimum value requirement as long as it is positive.
The amount that will be burned is currently configured to be in between 2% and 50% according to vesting time.
`burn_amount` is calculated as:

Here, `amount`...

Vulnerable code:
let burn_amount = amount
            .saturating_mul(burn_pct)
            .checked_div(100)
            .ok_or(ErrorCode::ArithmeticError)?;

let burn_amount: u64 = amount
            .saturating_mul(burn_pct)
+           .checked_add(99)
+           .ok_or(ErrorCode::ArithmeticError)?;
            .checked_div(100)
            .ok_or(ErrorCode::ArithmeticError)?;
LOW severity: Steep Stepwise Jumps in Burn Percentages. **Description:** `burn_pct` (burn percentage) does not utilize any precision multiplier and its value can be minimum **2** and maximum **50** with initial configuration. Calculation for `burn_pct` as follows:

Changing these variables with their initial configuration, we can approximate the...

Vulnerable code:
(state.min_burn_pct as u64)
                .saturating_sub(((over_min * burn_range) / time_range) as u64)

50 - 48 * ( (vest_duration - 10 days ) / 170 days )

-        state.min_burn_pct = 50; // 50% @ min
-        state.max_burn_pct = 2; //  2% @ max
+        state.min_burn_pct = 5_000; // 50% in basis points (bips) @ min
+        state.max_burn_pct = 200; //  2% in basis points (bips) @ max
HIGH severity: StakeCounter Struct Declared but Never Utilized. **Description:** The `clmm_lp_farming` program has a struct `StakeCounter` that includes a field `count`. However this struct is not utilized anywhere in the program:

**Recommended Mitigation:** Consider removing `StakeCounter` struct with its `LEN` implementation.

**Doryoku:**
Fixed in...

Vulnerable code:
#[account]
pub struct StakeCounter {
    pub count: u64,
}

impl StakeCounter {
    pub const LEN: usize = 8;
}
LOW severity: No need to bound the fee override in `FeeOverrideHooklet::setFeeOverride` as it is clamped in `HookletLib::beforeSwap`. **Description:** `FeeOverrideHooklet::setFeeOverride` bounds the overridden fees to prevent them from exceeding `SWAP_FEE_BASE`:

Assuming L-01 is correctly mitigated as suggested, it is not strictly necessary to perform this validation in the hooklet as fees will be clamped by the...

Vulnerable code:
function setFeeOverride(
    PoolId id,
    bool overrideZeroToOne,
    uint24 feeZeroToOne,
    bool overrideOneToZero,
    uint24 feeOneToZero
) public {
    if (feeZeroToOne >= SWAP_FEE_BASE || feeOneToZero >= SWAP_FEE_BASE) {
        revert FeeOverrideHooklet__InvalidSwapFee();
    }

    ...
}

function setFeeOverride(
        PoolId id,
        bool overrideZeroToOne,
        uint24 feeZeroToOne,
        bool overrideOneToZero,
        uint24 feeOneToZero
    ) public {
-       if (feeZeroToOne >= SWAP_FEE_BASE || feeOneToZero >= SWAP_FEE_BASE) {
-           revert FeeOverrideHooklet__InvalidSwapFee();
-       }

        ...
    }
MEDIUM severity: Incorrect usage of minOutputAmount in executeTwoStepRedemption can cause unnecessary reverts. **Description:** In the `RedemptionManager::executeTwoStepRedemption` function, the following call is made:

Here, `params.minOutputAmount` is used as the minimum expected return from the liquidity provider. However, this value does not account for any fee deductions that are applied later in the...

Vulnerable code:
params.liquidityProvider.supplyTo(contractAddress, params.liquidityTokenAmount, params.minOutputAmount);

uint256 offRampBalance = params.liquidityProvider.liquidityToken().balanceOf(contractAddress);
uint256 fee = _getFee(params.feeManager, offRampBalance);

if (offRampBalance - fee < params.minOutputAmount) {
    revert Errors.SlippageControlError();
}

uint256 expectedFee = _getFee(params.feeManager, params.minOutputAmount);
params.liquidityProvider.supplyTo(contractAddress, params.liquidityTokenAmount, params.minOutputAmount + expectedFee);
HIGH severity: Incorrect address handling for account abstraction wallets in `SecuritizeBridge::bridgeDSTokens`. **Description:** The `SecuritizeBridge.bridgeDSTokens` function assumes that a user's wallet address (`msg.sender`) on the source chain is identical to their desired recipient address on the destination chain. This assumption is incorrect for Account Abstraction (AA) wallets.

Account Abstraction...

Vulnerable code:
function bridgeDSTokens(
    uint16 targetChain,
    uint256 value,
    address destinationWallet
) external override payable whenNotPaused {
    require(destinationWallet != address(0), "Invalid destination wallet");

    // ... existing validation code ...

    wormholeRelayer.sendPayloadToEvm{value: msg.value} (
        targetChain,
        targetAddress,
        abi.encode(
            investorDetail.investorId,
            value,
            destinationWallet, // ← User-specified destination
            investorDetail.country,
            investorDetail.attributeValues,
            investorDetail.attributeExpirations
        ),
        0,
        gasLimit,
        whChainId,
        msg.sender
    );
}
MEDIUM severity: Pause modifier in bridge receiver functions causes receiver failures for in-flight messages. **Description:** The `USDCBridge::receivePayloadAndUSDC` and `SecuritizeBridge::receiveWormholeMessages` functions are protected by the `whenNotPaused` modifier, which causes these functions to revert when the respective bridge contracts are paused. According to the [Wormhole...

Vulnerable code:
// USDCBridge.sol
function receivePayloadAndUSDC(
    bytes memory payload,
    uint256 amountUSDCReceived,
    bytes32 sourceAddress,
    uint16 sourceChain,
    bytes32 deliveryHash
) internal override onlyWormholeRelayer whenNotPaused {
    // Function will revert if contract is paused
    // ...
}

// SecuritizeBridge.sol
function receiveWormholeMessages(
    bytes memory payload,
    bytes[] memory additionalVaas,
    bytes32 sourceBridge,
    uint16 sourceChain,
    bytes32 deliveryHash
) public override payable whenNotPaused {
    // Function will revert if contract is paused
    // ...
}
MEDIUM severity: Calculation of available liquidity in `CollateralLiquidityProvider::availableLiquidity` assumes 1:1 ratio between collateral asset and liquidity tokens. **Description:** The `CollateralLiquidityProvider::availableLiquidity` function incorrectly returns the balance of the collateral asset held by the collateral provider, assuming a 1:1 ratio between the collateral asset and the liquidity tokens that will actually be provided to redeemers. This...

Vulnerable code:
function availableLiquidity() external view returns (uint256) {
    return IERC20(externalCollateralRedemption.asset()).balanceOf(collateralProvider);
}

function _availableLiquidity() private view returns (uint256) {
    return IERC20(externalCollateralRedemption.asset()).balanceOf(collateralProvider);
}

function supplyTo(
    address redeemer,
    uint256 amount,
    uint256 minOutputAmount
) public whenNotPaused onlySecuritizeRedemption {
    if (amount > _availableLiquidity()) {
        revert InsufficientLiquidity(amount, _availableLiquidity());
    }

    // ... collateral transfer and redemption logic ...

    // The actual liquidity provided is calculated here, not the raw collateral amount
    uint256 assetsAfterExternalCollateralRedemptionFee = externalCollateralRedemption.calculateLiquidityTokenAmount(
        amount
    );

    liquidityToken.transfer(redeemer, assetsAfterExternalCollateralRedemptionFee);
}

function availableLiquidity() external view returns (uint256) {
-    return IERC20(externalCollateralRedemption.asset()).balanceOf(collateralProvider);
+    return _availableLiquidity();
}

function _availableLiquidity() private view returns (uint256) {
-    return IERC20(externalCollateralRedemption.asset()).balanceOf(collateralProvider);
+    uint256 collateralBalance = IERC20(externalCollateralRedemption.asset()).balanceOf(collateralProvider);
+    return externalCollateralRedemption.calculateLiquidityTokenAmount(collateralBalance);
}
LOW severity: Missing validation check for liquidityToken. **Description:** The `CollateralLiquidityProvider::initialize()` function does **not verify** that the `liquidityToken` of the passed `securitizeOffRamp`'s `liquidityProvider()` matches the expected token. This check is present in `setExternalCollateralRedemption()` but missing here.

**Impact:**...

Vulnerable code:
function initialize(
    address _liquidityToken,
    address _recipient,
    address _securitizeOffRamp
) public onlyProxy initializer {
    if (_recipient == address(0) || _liquidityToken == address(0) || _securitizeOffRamp == address(0)) {
        revert NonZeroAddressError();
    }

    address expectedToken = ILiquidityProvider(
        ISecuritizeOffRamp(_securitizeOffRamp).liquidityProvider()
    ).liquidityToken();

    if (expectedToken != _liquidityToken) {
        revert LiquidityTokenMismatch();
    }

    __BaseContract_init();
    recipient = _recipient;
    liquidityToken = IERC20(_liquidityToken);
    securitizeOffRamp = ISecuritizeOffRamp(_securitizeOffRamp);
}
LOW severity: `liquidityProviderWallet` is not set during initialization. **Description:** In `AllowanceLiquidityProvider::initialize`, one of the key properties, `liquidityProviderWallet`, is not initialized. This property is declared as a public variable but never set during contract initialization. Since the contract does not enforce or set this value at any point in.....

Vulnerable code:
address public liquidityProviderWallet;

function initialize(
    address _liquidityToken,
    address _recipient,
    address _securitizeOffRamp,
    address _liquidityProviderWallet
) public onlyProxy initializer {
    if (_recipient == address(0)) revert NonZeroAddressError();
    if (_liquidityToken == address(0)) revert NonZeroAddressError();
    if (_securitizeOffRamp == address(0)) revert NonZeroAddressError();
    if (_liquidityProviderWallet == address(0)) revert NonZeroAddressError();

    __BaseContract_init();
    recipient = _recipient;
    liquidityToken = IERC20(_liquidityToken);
    securitizeOffRamp = ISecuritizeOffRamp(_securitizeOffRamp);
    liquidityProviderWallet = _liquidityProviderWallet;
}
LOW severity: Single-step ownership transfer pattern is not recommended. **Description:** The two `BaseContract` implementations currently inherit from OpenZeppelin's `OwnableUpgradeable`, which uses a **single-step ownership transfer** pattern. This approach is risky: if an incorrect address is set as the new owner, the contract may become permanently inaccessible to...

Vulnerable code:
-abstract contract BaseContract is UUPSUpgradeable, PausableUpgradeable, OwnableUpgradeable {
+abstract contract BaseContract is UUPSUpgradeable, PausableUpgradeable, Ownable2StepUpgradeable {
LOW severity: `whChainId` should not be stored as immutable constant. **Description:** The `SecuritizeBridge` contract's use of an immutable `whChainId` constant, set during deployment, creates a critical vulnerability during chain forks. If the stored whChainId no longer matches the actual `chain ID`(block.chainid), cross-chain operations via `sendPayloadToEvm` may.....

Vulnerable code:
// Remove: uint16 public immutable whChainId;

function getCurrentChainId() public view returns (uint16) {
    return uint16(block.chainid);
}

// In bridgeDSTokens function:
wormholeRelayer.sendPayloadToEvm{value: msg.value} (
    targetChain,
    targetAddress,
    abi.encode(/* payload data */),
    0,
    gasLimit,
    getCurrentChainId(),
    msg.sender
);
LOW severity: Refund address in `bridgeDSTokens` function should be configurable. **Description:** In the `SecuritizeBridge::bridgeDSTokens` function, `msg.sender` is assigned as the refund address.

Wormhole will refund the leftover gas to `refundAddress`. However, if the `msg.sender` is a smart contract that does not implement a function to withdraw the native gas token, any...

Vulnerable code:
// Send Relayer message
        wormholeRelayer.sendPayloadToEvm{value: msg.value} (
            targetChain,
            targetAddress,
            abi.encode(
                investorDetail.investorId,
                value,
                msg.sender,
                investorDetail.country,
                investorDetail.attributeValues,
                investorDetail.attributeExpirations
            ), // payload
            0, // no receiver value needed since we"re just passing a message
            gasLimit,
            whChainId,
            msg.sender //@audit refund address, any leftover gas will be sent to this address
        );

function bridgeDSTokens(..., address refundAddress) external {
    require(refundAddress != address(0), "Invalid refund address");
    ...
}
HIGH severity: Ambiguous `-1` return value in `PredictionMarketV3_4::getMarketResolvedOutcome`. **Description:** In [`PredictionMarketV3_4::getMarketResolvedOutcome`](https://github.com/Polkamarkets/polkamarkets-js/blob/24f1394be94d27433d2e3a7370442126e1c1e5ba/contracts/PredictionMarketV3_4.sol#L1336-L1345), there is logic to indicate when a market resolution is still pending:

The function...

Vulnerable code:
function getMarketResolvedOutcome(uint256 marketId) public view returns (int256) {
  Market storage market = markets[marketId];

  // returning -1 if market still not resolved
  if (market.state != MarketState.resolved) {
    return -1;
  }

  return int256(market.resolution.outcomeId);
}
LOW severity: Markets Stuck in 'Open' State even if `block.timestamp > markets[marketId].closesAtTimestamp == true`. **Description:** Functions  `_buy`, `_sell`, `_addLiquidity` or `_removeLiquidity` use the `timeTransitions(marketId)` modifier followed by `atState(marketId, MarketState.open)`.

If someone tries to do one of these operations in this context: `block.timestamp > markets[marketId].closesAtTimestamp.....

Vulnerable code:
function test_MarketStuckInOpenState() public {
        // Create market that closes in 1 hour
        uint32 closeTime = uint32(block.timestamp + 3600);

        PredictionMarketV3_4.CreateMarketDescription memory desc = PredictionMarketV3_4.CreateMarketDescription({
            value: VALUE,
            closesAt: closeTime,
            outcomes: 2,
            token: IERC20(address(tokenERC20)),
            distribution: new uint256[](0),
            question: "PoC Market State Issue",
            image: "test",
            arbitrator: address(0x1),
            buyFees: PredictionMarketV3_4.Fees({fee: 0, treasuryFee: 0, distributorFee: 0}),
            sellFees: PredictionMarketV3_4.Fees({fee: 0, treasuryFee: 0, distributorFee: 0}),
            treasury: treasury,
            distributor: distributor,
            realitioTimeout: 3600,
            manager: IPredictionMarketV3Manager(address(manager))
        });

        uint256 marketId = predictionMarket.createMarket(desc);

        // Verify market is initially open
        (PredictionMarketV3_4.MarketState state, uint256 closesAt,,,,) = predictionMarket.getMarketData(marketId);
        assertEq(uint256(state), uint256(PredictionMarketV3_4.MarketState.open));
        assertEq(closesAt, closeTime);

        console.log("BEFORE TIME WARP");
        console.log("Market state (0=open, 1=closed, 2=resolved):", uint256(state));
        console.log("Time until close:", closesAt - block.timestamp);

        // TIME WARP: Move 2 hours into the future (1 hour past close time)
        vm.warp(block.timestamp + 7200);

        console.log("AFTER TIME WARP");
        console.log("Market closes at:", closesAt);
        console.log("Time past close:", block.timestamp - closesAt);

        (state,,,,,) = predictionMarket.getMarketData(marketId);
        console.log("Market state (should be 1=closed, but shows):", uint256(state));

        // Prove the market is logically closed but state is wrong
        assertTrue(block.timestamp > closesAt, "We are past close time");
        assertEq(uint256(state), uint256(PredictionMarketV3_4.MarketState.open), "Market incorrectly shows as open!");

        // ATTEMPT TO TRADE: This should fail due to modifier ordering issue
        console.log("ATTEMPTING TRADE (should fail)");

        address trader = makeAddr("trader");
        deal(address(tokenERC20), trader, 1 ether);

        vm.startPrank(trader);
        tokenERC20.approve(address(predictionMarket), type(uint256).max);
        vm.expectRevert(bytes("!ms")); // Market state error
        predictionMarket.buy(marketId, 0, 0, 0.1 ether);
        vm.stopPrank();

        console.log("Trade failed as expected due to modifier");
    }
LOW severity: Operations done right at the `closesAtTimestamp`. **Description:** The protocol checks the time validity of all user facing methods through the following modifier:

This condition `block.timestamp > markets[marketId].closesAtTimestamp` excludes the case where `block.timestamp = markets[marketId].closesAtTimestamp`, allowing buy, sell,...

Vulnerable code:
modifier timeTransitions(uint256 marketId) {
    if (block.timestamp > markets[marketId].closesAtTimestamp && markets[marketId].state == MarketState.open) {
      _nextState(marketId);
    }
    _;
  }

function testTradeRightAtMarketClose() public {
        uint256 marketId = _createTestMarket();

        // Get market close time
        (,uint256 closesAt,,,,) = predictionMarket.getMarketData(marketId);

        // Set timestamp to exact close time
        vm.warp(closesAt);

        // This should fail - market transitions to closed due to timeTransitions modifier // NB it doesn't
        // vm.expectRevert(bytes("!ms")); // Market state error
        predictionMarket.buy(marketId, 0, 0, VALUE);
    }


    function testLiquidityOperationsAtMarketClose() public {
        uint256 marketId = _createTestMarket();

        // Get market close time
        (,uint256 closesAt,,,,) = predictionMarket.getMarketData(marketId);

        // Add more liquidity before close
        vm.warp(closesAt - 1);
        predictionMarket.addLiquidity(marketId, VALUE);

        // Try to add liquidity at exact close time - should fail // NB it doesn't
        vm.warp(closesAt);
        // vm.expectRevert(bytes("!ms"));
        predictionMarket.addLiquidity(marketId, VALUE);
    }

modifier timeTransitions(uint256 marketId) {
--    if (block.timestamp > markets[marketId].closesAtTimestamp && markets[marketId].state == MarketState.open) {
++    if (block.timestamp >= markets[marketId].closesAtTimestamp && markets[marketId].state == MarketState.open) {
      _nextState(marketId);
    }
    _;
  }
LOW severity: Lack of slippage protection in liquidity functions. **Description:** The `addLiquidity` and `removeLiquidity` functions do not include any form of slippage protection, such as `minSharesOut` or `minTokensOut` parameters. This means users cannot guard against receiving significantly fewer shares or tokens than expected due to changes in pool state...

Vulnerable code:
function testFrontRunAddLiquidity() public {
        uint256 marketId = _createTestMarket();

        address alice = makeAddr("Alice");
        address bob = makeAddr("Bob");
        deal(address(tokenERC20), alice, 1 ether);
        deal(address(tokenERC20), bob, 1 ether);

        // bob sees that alice will add liquidity and buys shares to manipulate the price
        vm.startPrank(bob);
        tokenERC20.approve(address(predictionMarket), type(uint256).max);
        predictionMarket.buy(marketId, 1, 0, 10e16);
        vm.stopPrank();

        // alice adds liquidity
        vm.startPrank(alice);
        tokenERC20.approve(address(predictionMarket), type(uint256).max);
        predictionMarket.addLiquidity(marketId, 1e16);
        vm.stopPrank();

        // bob can now sell for more gaining ~30% of Alice deposit
        vm.prank(bob);
        predictionMarket.sell(marketId, 1, 10.3e16, type(uint256).max);
    }

function addLiquidity(uint256 marketId, uint256 amount, uint256 minSharesOut) external;
function removeLiquidity(uint256 marketId, uint256 shares, uint256 minTokensOut) external;
LOW severity: Consider adding a `deadline` parameter to user facing calls. **Description:** The `buy`, `sell`, `addLiquidity`, and `removeLiquidity` functions currently do not accept a `deadline` parameter to constrain when a transaction must be executed. Without a deadline, transactions may be mined at unexpected times, especially if there are delays in submission or...

Vulnerable code:
modifier onlyBefore(uint256 deadline) {
    require(block.timestamp <= deadline, "!d");
    _;
}