Farseen0/scar-pairs huggingface dataset:
LOW severity: Consider adding share-based sell function to avoid dust shares. **Description:** The current interface requires users to specify the desired amount out (tokens) when selling shares, rather than the amount in (shares to sell). This makes it difficult for users or integrators to accurately sell all their shares, especially when prices fluctuate during...

Vulnerable code:
function sellShares(uint256 marketId, uint256 outcome, uint256 shareAmount, uint256 minAmountOut) external;

// x = token amount user will receive
// shares = shares sold
// S = sell token pool balance
// O = other outcome pool balance

x² - x * (O + S + shares) + shares * O = 0
=> x = 1/2 (-sqrt(O^2 - 2 O (shares + S) + 4 * O * S + (shares + S)^2) + O + shares + S)

x³ - x²(shares + S + O₁ + O₂) + x((shares + S)(O₁ + O₂) + O₁O₂) - shares × O₁ × O₂ = 0
HIGH severity: Disallow single-outcome markets. **Description:** When creating a market in [`PredictionMarketV3_4::_createMarket`](https://github.com/Polkamarkets/polkamarkets-js/blob/24f1394be94d27433d2e3a7370442126e1c1e5ba/contracts/PredictionMarketV3_4.sol#L299-L356), the following...

Vulnerable code:
require(desc.outcomes > 0 && desc.outcomes <= MAX_OUTCOMES, "!oc");

require(desc.outcomes > 1 && desc.outcomes <= MAX_OUTCOMES, "!oc");
HIGH severity: Add explicit check to prevent underflow revert in `PredictionMarketV3_4::calcSellAmount`. **Description:** In [`PredictionMarketV3_4::calcSellAmount`](https://github.com/Polkamarkets/polkamarkets-js/blob/24f1394be94d27433d2e3a7370442126e1c1e5ba/contracts/PredictionMarketV3_4.sol#L434) following line may revert due to an underflow if the user attempts to sell too many shares:

If...

Vulnerable code:
endingOutcomeBalance = (endingOutcomeBalance * outcomeShares).ceilDiv(outcomeShares - amountPlusFees);

require(amountPlusFees < outcomeShares, "a>s");
HIGH severity: Unnecessary transfer in `PredictionMarketV3_4::mintAndCreateMarket`. **Description:** [`PredictionMarketV3_4::mintAndCreateMarket`](https://github.com/Polkamarkets/polkamarkets-js/blob/24f1394be94d27433d2e3a7370442126e1c1e5ba/contracts/PredictionMarketV3_4.sol#L381-L390) does a mint of `FantasyERC20` to the user then a transfer to the contract:

The issue is that...

Vulnerable code:
function mintAndCreateMarket(CreateMarketDescription calldata desc) external nonReentrant returns (uint256 marketId) {
  // mint the amount of tokens to the user
  IFantasyERC20(address(desc.token)).mint(msg.sender, desc.value);

  marketId = _createMarket(desc);
  // transferring funds
  desc.token.safeTransferFrom(msg.sender, address(this), desc.value);

  return marketId;
}

function mintAndCreateMarket(CreateMarketDescription calldata desc) external nonReentrant returns (uint256 marketId) {
  // mint the amount of tokens to the user
  IFantasyERC20(address(desc.token)).mint(address(this), desc.value);

  return _createMarket(desc);
}
LOW severity: Mismatched total supply cap between and tokens. **Description:** OpenZeppelin’s [`ERC20VotesUpgradeable`](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/release-v5.4/contracts/token/ERC20/extensions/ERC20VotesUpgradeable.sol#L45-L47) enforces:

to keep vote‑checkpoint values within 208 bits. As a result, any L1 total...

Vulnerable code:
function _maxSupply() internal view virtual returns (uint256) {
    return type(uint208).max;
}
HIGH severity: `Minter` doesn't account for depegs, exchange rates and decimal precision mismatch between `baseAsset` and `hilBTCToken`, which can result in total loss scenarios for both users and protocol. **Description:** `Minter` supports conversion between two key assets, `baseAsset` and `hilBTCToken`:

* `baseAsset` explicitly can be `wBTC`, `USDC` or `USDT`; these can have various precision such as 8, 6 or 18 depending on the chain
* `hilBTCToken` is explicitly always the protocol's synthetic...

Vulnerable code:
/// @notice The token address of the base asset accepted for deposit (e.g., WBTC or a stablecoin)
    IERC20 public immutable baseAsset;

    /// @notice The token address of the HilBTC ERC-20 contract (which the Minter will mint/burn)
    IMintableERC20 public immutable hilBTCToken;

function mint(
        address to,
        uint256 amount
    ) external onlyWhitelisted(msg.sender) onlyWhitelisted(to) nonReentrant {
        baseAsset.safeTransferFrom(msg.sender, address(this), amount);
        hilBTCToken.mint(to, amount);
        totalDeposits += amount;
        emit Minted(to, amount);
    }

/// @notice The token address of the base asset accepted for deposit (e.g., WBTC or a stablecoin)
    IERC20 public immutable baseAsset;
LOW severity: Non-compliant users can claim withdrawn assets after the cooldown period. **Description:** `StakingVault::redeem, withdraw` use the `onlyWhitelisted` modifier to verify that `msg.sender` is whitelisted or is compliant, as this modifier ends up calling `Whitelist::isAddressWhitelisted`:

If a user was whitelisted or was compliant when they created the...

Vulnerable code:
function isAddressWhitelisted(address user) public view returns (bool) {
    if (manualWhitelist[user] || globalWhitelist) {
        return true;
    }

    return complianceChecker.isCompliant(user);
}
LOW severity: Missing check if `receiver` is whitelisted in `StakingVault::mint, deposit`. **Description:** `StakingVault::mint, deposit` only validates that `msg.sender` is whitelisted but fails to check if the receiver parameter is whitelisted. Since non-whitelisted addresses cannot withdraw, redeem, or transfer shares, any shares minted to non-whitelisted receivers become permanently.....

Vulnerable code:
function mint(
        uint256 shares,
        address receiver //@audit receiver could be not whitelisted?
    ) public override onlyWhitelisted(msg.sender) returns (uint256 assets) {
        ...
    }

function test_mint_non_whitelist_receiver() public {
        uint256 amount = 100 ether;

        vm.startPrank(user1);
        asset.approve(address(vault), amount);

        //create a non whitelisted receiver
        address bob = makeAddr("receiver");

        // 1. Alice (whitelisted) mints shares to Bob (non-whitelisted)
        vault.mint(1000 * 1e8, bob); // Success - only checks Alice is whitelisted

        vm.stopPrank();

        // 2. Bob tries to withdraw - REVERTS
        vm.prank(bob);
        vault.withdraw(1000 * 1e8, bob, bob); // Reverts: not whitelisted
        // Result: 1000 shares worth of HilBTC permanently locked
    }

function mint(
    uint256 shares,
    address receiver
+ ) public override onlyWhitelisted(msg.sender) onlyWhitelisted(receiver) returns (uint256 assets) {
-   ) public override onlyWhitelisted(msg.sender) returns (uint256 assets) {
 ...
}
// Similar fix to `deposit`
LOW severity: Unbounded `depositAddresses` can cause `CompliantDepositRegistry::challengeLatestBatch` to revert due to out of gas. **Description:** `CompliantDepositRegistry::challengeLatestBatch` contains an unbounded loop that removes deposit addresses from the latest batch by calling `depositAddresses.pop()` repeatedly. When a large batch of deposit addresses is added via `CompliantDepositRegistry::addDepositAddresses`,...

Vulnerable code:
function challengeLatestBatch() public onlyRole(CANCELER_ROLE) {
        require(latestBatchUnlockTime >= block.timestamp, NoChallengeAfterUnlock());

        uint256 _finalizedAddressesLength = finalizedAddressesLength;
        // Get rid of the challenged batch by removing it from the list
        uint256 batchLength = depositAddresses.length - _finalizedAddressesLength;
        for (uint256 i; i < batchLength; i++) {
            depositAddresses.pop();
        }<---------

        // Reset the challenge period to allow a new batch to be generated
        latestBatchUnlockTime = block.timestamp;

        emit BatchChallenged(_finalizedAddressesLength, block.timestamp, batchLength);
    }
LOW severity: Malicious user holding `HilBTC` tokens can front run blacklisted transaction. **Description:** When the `blacklister` attempts to blacklist a malicious user holding `HilBTC` tokens, the maliciois user can monitor the memepool and  front-run the blacklist tx by quickly redeeming their `HilBTC` tokens through the `minter` contract before the blacklist takes effect. Once...

Vulnerable code:
function redeem(
        uint256 amount
    ) external onlyWhitelisted(msg.sender) nonReentrant {
        hilBTCToken.burnFrom(msg.sender, amount);
        baseAsset.safeTransfer(msg.sender, amount);
        totalDeposits -= amount;
        emit Redeemed(msg.sender, amount);
    }
LOW severity: Owner can not burn tokens from blacklisted addresses. **Description:** The `HilBTC.sol` contract contains a logical inconsistency in its `burnFrom()` function regarding blacklisted addresses. While the function grants the `owner` special privileges to burn tokens from any user without requiring allowance (bypassing the `_spendAllowance` check), the...

Vulnerable code:
function burnFrom(address from, uint256 amount) external {
        address spender = msg.sender;
        if (spender != from && spender != owner()) {
            _spendAllowance(from, spender, amount);
        }
        _burn(from, amount);
    } //@audit owner can not burn from Blacklisted

    function _update(address from, address to, uint256 amount)
        internal
        override
        notBlacklisted(from)
        notBlacklisted(to)
    {
        super._update(from, to, amount);
    }
HIGH severity: Don't initialize to default values in Solidity. **Description:** Don't initialize to default values in Solidity:
* `Deposit-Registry`:

**Syntetika:**
Fixed in commit [7c69e94](https://github.com/SyntetikaLabs/monorepo/commit/7c69e94d165cfe4b78ace518d622e25654e482d3).

**Cyfrin:** Verified.

Vulnerable code:
ComplianceChecker.sol
44:        for (uint i = 0; i < complianceOptions.length; i++) {
58:            uint optionIndex = 0;
65:                uint sbtIndex = 0;

CompliantDepositRegistry.sol
133:            uint i = 0;
157:        for (uint i = 0; i < newDepositAddresses.length; i++) {
200:        for (uint i = 0; i < batchLength; i++) {
HIGH severity: Prefer explicit `uint` sizes. **Description:** Prefer explicit `uint` sizes:
* `Issuance`

* `Deposit-Registry`:

**Syntetika:**
Fixed in commit [cb00843](https://github.com/SyntetikaLabs/monorepo/commit/cb0084368c85c4878dfd0af0fbca1c4924a36497).

**Cyfrin:** Verified.

Vulnerable code:
interfaces/minter/IBridgeMinter.sol
14:        uint id,
29:        uint id;

interfaces/galactica/IVerificationSBT.sol
45:        uint _expirationTime,
55:        uint _expirationTime

interfaces/token/IMintableERC20.sol
7:    function mint(address to, uint amount) external;
8:    function burn(address from, uint amount) external;

vault/StakingVault.sol
143:        uint assetsRedeemed = _redeemTo(shares, address(tokensHolder));
289:    ) internal returns (uint shares) {
306:    ) internal returns (uint assets) {

interfaces/ICompliantDepositRegistry.sol
44:        uint indexed startIndex,
45:        uint challengePeriodEnd,
48:    event BatchChallengePeriodSet(uint newChallengePeriod);
50:        uint indexed startIndex,
51:        uint challengePeriodEnd,
52:        uint batchLength
100:        uint startIndex,
101:        uint count
116:    function setBatchChallengePeriod(uint newChallengePeriod) external;

ComplianceChecker.sol
44:        for (uint i = 0; i < complianceOptions.length; i++) {
58:            uint optionIndex = 0;
65:                uint sbtIndex = 0;

CompliantDepositRegistry.sol
27:    uint public nextDepositAddressIndex;
30:    uint public batchChallengePeriod;
32:    uint public latestBatchUnlockTime;
34:    uint public finalizedAddressesLength;
44:        // Block the first deposit address so that the default uint does not point to a valid address
124:        uint startIndex,
125:        uint count
127:        uint returnLength = count;
133:            uint i = 0;
156:        uint startIndex = depositAddresses.length;
157:        for (uint i = 0; i < newDepositAddresses.length; i++) {
174:    function _setBatchChallengePeriod(uint newChallengePeriod) internal {
184:        uint newChallengePeriod
199:        uint batchLength = depositAddresses.length - finalizedAddressesLength;
200:        for (uint i = 0; i < batchLength; i++) {
HIGH severity: Use named imports. **Description:** Use named imports; this is already being done in some places but not others:
* `Issuance`:

* `Deposit-Registry`:

**Syntetika:**
Fixed in commit [a8b4853](https://github.com/SyntetikaLabs/monorepo/commit/a8b485381ad97ffb01595e8e5cc3d479126fcee8).

**Cyfrin:** Verified.

Vulnerable code:
minter/Minter.sol
4:import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
5:import "@openzeppelin/contracts/access/AccessControl.sol";
6:import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

vault/StakingVault.sol
5:import "../interfaces/vault/IStakingVault.sol";
7:import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

helpers/TokensHolder.sol
4:import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

token/HilBTC.sol
4:import "@openzeppelin/contracts/access/AccessControl.sol";

interfaces/ICompliantDepositRegistry.sol
4:import "@openzeppelin/contracts/access/IAccessControl.sol";
5:import "./IComplianceChecker.sol";

interfaces/IComplianceChecker.sol
4:import "@openzeppelin/contracts/access/IAccessControl.sol";
5:import "@galactica-net/zk-certificates/contracts/interfaces/IVerificationSBT.sol";

ComplianceChecker.sol
4:import "@openzeppelin/contracts/access/AccessControl.sol";
5:import "./interfaces/IComplianceChecker.sol";

CompliantDepositRegistry.sol
4:import "@openzeppelin/contracts/access/AccessControl.sol";
5:import "./interfaces/IComplianceChecker.sol";
6:import "./interfaces/ICompliantDepositRegistry.sol";
HIGH severity: Consider using `SafeCast` when downcasting amounts. **Description:** Consider using [SafeCast](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/SafeCast.sol) when downcasting amounts:
* `StakingVault.sol`:

**Syntetika:**
Fixed in commit...

Vulnerable code:
144:        cooldowns[msg.sender].underlyingAmount += uint152(assetsRedeemed);
165:        cooldowns[msg.sender].underlyingAmount += uint152(assets);
HIGH severity: Roles not set in `deposit-registry` contract constructors. **Description:** Roles in contracts that belong to the `issuance` contracts are being initialized in the constructors and contain proper functions to update the address for this roles (revoke and grant):

But roles are not being initialized in the constructors of contracts in...

Vulnerable code:
constructor(
        string memory _name,
        string memory _symbol,
        address _initialAdmin,
        address _minter
    ) ERC20(_name, _symbol) Ownable(_initialAdmin) {
        require(
            _minter != address(0) && _initialAdmin != address(0),
            AddressCantBeZero()
        );
        minter = _minter;
        blacklister = _initialAdmin;
        _grantRole(DEFAULT_ADMIN_ROLE, _initialAdmin);
        _grantRole(MINTER_ROLE, _minter);
    }

function setMinter(
        address newMinter
    ) external onlyRole(DEFAULT_ADMIN_ROLE) {
        require(newMinter != address(0), AddressCantBeZero());
        revokeRole(MINTER_ROLE, minter);
        minter = newMinter;
        _grantRole(MINTER_ROLE, newMinter);
    }

bytes32 public constant COMPLIANCE_ADMIN_ROLE = keccak256("COMPLIANCE_ADMIN_ROLE");

    constructor(address defaultAdmin) {
        // The defaultAdmin can grant other roles later
        _grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);
    }
MEDIUM severity: Shared configuration parameters across different asset types in vault deployers leads to incorrect pricing and fee calculations. **Description:** The `VaultDeployer` and `SecuritizeVaultDeployer` contracts maintain shared configuration parameters (`navProvider`, `feeManager`, `redemptionAddress`) that are applied to all deployed vaults regardless of their underlying asset type. When `SegregatedVaultDeployer::deploy()` or...

Vulnerable code:
// In SecuritizeVaultDeployer::deploy()
BeaconProxy proxy = new BeaconProxy(
    upgradeableBeacon,
    abi.encodeWithSelector(
        SecuritizeVaultV2(payable(address(0))).initializeV2.selector,
        name,
        symbol,
        assetToken,      // Different per deployment
        redemptionAddress, // Same for all deployments - ISSUE
        liquidationToken,
        navProvider,     // Same for all deployments - ISSUE
        feeManager       // Same for all deployments - ISSUE
    )
);

+ mapping(address => address) public assetNavProviders;
+ mapping(address => address) public assetFeeManagers;
+ mapping(address => address) public assetRedemptionAddresses;

+ function setAssetConfiguration(
+     address assetToken,
+     address navProvider,
+     address feeManager,
+     address redemptionAddress
+ ) external onlyRole(DEFAULT_ADMIN_ROLE) {
+     assetNavProviders[assetToken] = navProvider;
+     assetFeeManagers[assetToken] = feeManager;
+     assetRedemptionAddresses[assetToken] = redemptionAddress;
+ }
LOW severity: Missing storage gap in upgradeable parent contract causes storage slot collision risk. **Description:** The `VaultDeployer` abstract contract is designed to be upgradeable and inherited by child contracts `SegregatedVaultDeployer` and `SecuritizeVaultDeployer`. However, `VaultDeployer` lacks storage gap variables to reserve space for future upgrades.

Currently, `VaultDeployer`...

Vulnerable code:
abstract contract VaultDeployer is IVaultDeployer, BaseContract {
    bytes32 public constant AGGREGATOR_ROLE = keccak256("AGGREGATOR_ROLE");

    address public navProvider;
    address internal admin;
    address public upgradeableBeacon;

+   // Reserve storage slots for future VaultDeployer upgrades
+   uint256[47] private __gap;

    // ... rest of contract
}

abstract contract VaultDeployer is IVaultDeployer, BaseContract {
    /// @custom:storage-location erc7201:securitize.storage.VaultDeployer
    struct VaultDeployerStorage {
        address navProvider;
        address admin;
        address upgradeableBeacon;
    }

    // keccak256(abi.encode(uint256(keccak256("securitize.storage.VaultDeployer")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant VAULT_DEPLOYER_STORAGE_LOCATION = 0x...;

    function _getVaultDeployerStorage() private pure returns (VaultDeployerStorage storage $) {
        assembly {
            $.slot := VAULT_DEPLOYER_STORAGE_LOCATION
        }
    }

    // Update all variable access to use the storage struct
    // ... rest of contract
}
LOW severity: Upgradeable contract initializer not disabled in constructor allows implementation contract initialization. **Description:** The `SegregatedVault` contract is designed as an upgradeable contract using the UUPS (Universal Upgradeable Proxy Standard) pattern, inheriting from `BaseContract` which extends `UUPSUpgradeable`. However, the implementation contract fails to disable its initializer in the...

Vulnerable code:
contract SegregatedVault is ERC4626Upgradeable, ISegregatedVault, IVaultAccessControl, BaseContract {

+   /// @custom:oz-upgrades-unsafe-allow constructor
+   constructor() {
+       _disableInitializers();
+   }
LOW severity: Incomplete mapping updates in `setVault` function cause vault address inconsistencies. **Description:** The `RWASegWrap::setVault` function allows admins to update the vault address for a specific vault ID, but it fails to properly maintain the bidirectional mapping between vault addresses and vault IDs. The function only updates `vaults[id] = vault` but does not update the...

Vulnerable code:
function setVault(
    address vault,
    uint256 id
) public virtual override onlyRole(DEFAULT_ADMIN_ROLE) idNotZero(id) recognizedVault(id) addressNotZero(vault) {
    address oldVault = vaults[id];
    address investorWallet = vaultIdOwnerWallets[id];
    vaults[id] = vault;
+   delete vaultIds[oldVault];
+   vaultIds[vault] = id;
    emit VaultUpdated(oldVault, vault, id, investorWallet);
}
HIGH severity: Unsafe ERoperations can cause unexpected failures with non-standard tokens. **Description:** The protocol uses direct `IERC20.transferFrom` and `IERC20.approve` function calls instead of OpenZeppelin's `SafeERC20` library wrappers. This creates compatibility issues with tokens that do not return boolean values or have non-standard implementations.

The primary occurrence...

Vulnerable code:
// Add import to RWASegWrap.sol
+import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract RWASegWrap is ... {
+   using SafeERC20 for IERC20;

    function _pullAndApprove(address caller, uint256 assets, uint256 vaultId) internal {
        ISegregatedVault vault = ISegregatedVault(vaults[vaultId]);
-       bool success = IERC20(asset).transferFrom(caller, address(this), assets);
-       if (!success) {
-           revert AssetTransferFailed();
-       }
-       success = IERC20(asset).approve(address(vault), assets);
-       if (!success) {
-           revert AssetApprovalFailed();
-       }
+       IERC20(asset).safeTransferFrom(caller, address(this), assets);
+       IERC20(asset).forceApprove(address(vault), assets);
    }
}

// In SecuritizeVault.liquidate
-IERC20Metadata(asset()).approve(address(redemption), assets);
+IERC20Metadata(asset()).forceApprove(address(redemption), assets);

// In SecuritizeVaultV2._liquidateTo
-bool success = IERC20Metadata(asset()).approve(address(redemption), assets);
-if (!success) {
-    revert AssetApprovalFailed();
-}
+IERC20Metadata(asset()).forceApprove(address(redemption), assets);
LOW severity: Inconsistency between function name and documentation in `getUnderlyingAsset`. **Description:** The `RWASegWrap::getUnderlyingAsset` function has inconsistent naming and documentation. The function name `getUnderlyingAsset` suggests it should return the underlying asset address (the RWA token), but the interface documentation states "Gets the vault address for a given asset...

Vulnerable code:
// Interface documentation says: "Gets the vault address for a given asset ID"
// But the function name suggests it returns the underlying asset
function getUnderlyingAsset(uint256 id) external view returns (address);

// Implementation returns vault address, matching documentation but not the name
function getUnderlyingAsset(uint256 id) public virtual view override returns (address) {
    return vaults[id]; // Returns vault contract address
}

/**
 * @notice Gets the vault address for a given asset ID.
 * @dev Returns zero address if not found.
 */
- function getUnderlyingAsset(uint256 id) external view returns (address);
+ function getVaultAddress(uint256 id) external view returns (address);

/**
- * @notice Gets the vault address for a given asset ID.
+ * @notice Gets the underlying asset address for a given asset ID.
 * @dev Returns zero address if not found.
 */
function getUnderlyingAsset(uint256 id) external view returns (address);

// And update implementation:
function getUnderlyingAsset(uint256 id) public virtual view override returns (address) {
-   return vaults[id];
+   return vaults[id] != address(0) ? IERC4626(vaults[id]).asset() : address(0);
}