Farseen0/scar-pairs huggingface dataset:
HIGH severity: [FNG-16] Loans with CErcollateral can be made unliquidatable. **Severity:** High

**Path:** CErc721.sol:_seize#L470-L508

**Description:** The `_seize` function for CErc721 will always round up to a token amount for full NFTs. This function is used for reward a liquidator in `Comptroller.sol:batchLiquidateBorrow `with the value that came from a liquidated...

Vulnerable code:
uint oneNFTAmount = doubleScale / exchangeRateStoredInternal();
if (seizeTokens % oneNFTAmount != 0) {
    // ensure whole nft seize size by rounding up to the next whole NFT
    seizeTokens = ((seizeTokens / oneNFTAmount) + 1) * oneNFTAmount;
}

function _seize(address liquidator, address borrower, uint seizeTokens) override external nonReentrant returns (uint) {
        require(msg.sender == address(comptroller), "unauthorized");

        accrueInterest();

        uint oneNFTAmount = doubleScale / exchangeRateStoredInternal();
        if (seizeTokens % oneNFTAmount != 0) {
            // ensure whole nft seize size by rounding up to the next whole NFT
            seizeTokens = ((seizeTokens / oneNFTAmount) + 1) * oneNFTAmount;
        }

        /* Fail if seize not allowed */
        /*uint allowed = comptroller.seizeAllowed(address(this), seizerToken, liquidator, borrower, seizeTokens);
        if (allowed != 0) {
            revert LiquidateSeizeComptrollerRejection(allowed);
        }*/

        /* Fail if borrower = liquidator */
        if (borrower == liquidator) {
            revert LiquidateSeizeLiquidatorIsBorrower();
        }

        uint liquidatorSeizeTokens = seizeTokens;

        /////////////////////////
        // EFFECTS & INTERACTIONS
        // (No safe failures beyond this point)

        /* We write the calculated values into storage */
        accountTokens[borrower] = accountTokens[borrower] - seizeTokens;
        accountTokens[liquidator] = accountTokens[liquidator] + liquidatorSeizeTokens;

        /* Emit a Transfer event */
        emit Transfer(borrower, liquidator, liquidatorSeizeTokens);
        //emit Transfer(borrower, address(this), protocolSeizeTokens);
        //emit ReservesAdded(address(this), protocolSeizeAmount, totalReservesNew);

        return seizeTokens;
    }
LOW severity: [FNG-2] Inadequate constraints on Seize Share Mantissa in the protocol. **Severity:** Medium

**Path:** CToken.sol#L878-L894

**Description:** In the current implementation of the protocol, the `protocolSeizeShareMantissa` parameter in comparison with Compound is not constant (`CTokenInterface.sol` line 113) and can be adjusted by the admin. This lack of constraints...

Vulnerable code:
function _setProtocolSeizeShare(uint newProtocolSeizeShareMantissa) virtual override external returns (uint) {
        // Check caller is admin
        if (msg.sender != admin) {
            revert SetReserveFactorAdminCheck();
        }

        // Save current value for use in log
        uint oldProtocolSeizeShareMantissa = protocolSeizeShareMantissa;

        // Set liquidation incentive to new incentive
        protocolSeizeShareMantissa = newProtocolSeizeShareMantissa;

        // Emit event with old incentive, new incentive
        emit NewProtocolSeizeShare(oldProtocolSeizeShareMantissa, newProtocolSeizeShareMantissa);

        return NO_ERROR;
    }
LOW severity: [FNG-3] Missing maximum limit on stalePriceDelay. **Severity:** Low

**Path:** ChainlinkPriceOracle.sol#L150-L157

**Description:** In the contract `ChainlinkPriceOracle.sol` there is an issue related to the `stalePriceDelay` variable, which is used to determine how long it takes for a Chainlink price feed to be considered stale. Currently, this...

Vulnerable code:
function setStalePriceDelay(uint _stalePriceDelay) external {
        // Check caller = admin
        if (msg.sender != admin) {
            revert("unauthorized");
        }

        stalePriceDelay = _stalePriceDelay;
    }
LOW severity: [FNG-19] Custom errors. **Severity:** Low

**Description:** In various contracts the validation checks are performed using the `require` function with a reason string.

For example: 

**Remediation:**  We would recommend to replace these with custom errors. This should be done by flipping the check.

For...

Vulnerable code:
require(redeemTokensIn == 0 || redeemAmountIn == 0, "one of redeemTokensIn or redeemAmountIn must be zero");

require(X == Y, "X is not Y");

error XnotY(uint, uint);

if (X != Y)
  revert XnotY(X, Y);
HIGH severity: [FNG-22] Optimisation of NFT transfer out by ID. **Severity:** Low

**Path:** CERC721NoBorrow.sol:doNFTTransferOutByIds#L179-L214

**Description:**  `CERC721NoBorrow` implements a function `doNFTTransferOutByIds` to transfer NFTs out to the receiver using specific IDs.

In the function, it loops over each given NFT ID and the currently held NFTs.....

Vulnerable code:
function doNFTTransferOutByIds(address to, uint[] memory nftIds) internal {
        uint[] storage userHeldNFTs_ = userHeldNFTs[to];
        uint len = userHeldNFTs_.length;
        uint nftCount = nftIds.length;
        if (len < nftCount) {
            revert NftAmountTooHigh();
        }

        IERC721 underlying_ = IERC721(underlying);
        for (uint i = 0; i < nftCount;) {
            uint nftID = nftIds[i];
            uint assetIndex = len;
            for (uint j = 0; j < len;) {
                if (userHeldNFTs_[j] == nftID) {
                    assetIndex = j;
                    break;
                }
                unchecked { j++; }
            }

            if (assetIndex == len) {
                revert NftNotFound(nftID);
            }

            underlying_.transferFrom(address(this), to, nftID);

            // copy last item in list to location of item to be removed, reduce length by 1
            len = len - 1;
            userHeldNFTs_[assetIndex] = userHeldNFTs_[len];
            userHeldNFTs_.pop();

            unchecked { i++; }
        }

if (assetIndex != len) {
  userHeldNFTs_[assetIndex] = userHeldNFTs_[len];
}
HIGH severity: [FNG-18] Conditional early exit optimisation. **Severity:** Informational

**Description:** There are some conditionals that could be optimised by changing the order of elements by frequency/gas cost to allow for early exits and save gas on each function call.

We have identified the following locations:

1. `CErc721.sol:repayBorrowFresh` on...

Vulnerable code:
if (accountTokens[borrower] == 0 && accountBorrowsNew == 0) {
    comptroller.autoExitMarkets(borrower); // silent failure allowed
}

if (accountBorrowsNew == 0 && accountTokens[borrower] == 0) {
HIGH severity: [FNG-14] Function should be marked external. **Severity:** Informational

**Path:** CErc721.sol:borrowAndInterestBalanceStored#L715-L717

**Description:** The function `borrowAndInterestBalanceStored` is never called internally, therefore, there is no reason for it to be `public` instead of `external`.

**Remediation:**  We recommend...

Vulnerable code:
function borrowAndInterestBalanceStored(address account) public view returns (uint, uint) {
        return borrowAndInterestBalanceStoredInternal(account);
    }
HIGH severity: [FNG-7] Missing recovery mechanisms for ETH and ERtokens. **Severity:** Informational

**Path:** CErc20.sol:sweepToken#L134-L139

**Description:** The `sweepToken` function in the `CErc20.sol` contract is designed to recover accidental ERC-20 transfers made to this contract. However, this function does not account for accidentally sending ETH. As a...

Vulnerable code:
/**
     * @notice A public function to sweep accidental ERC-20 transfers to this contract. Tokens are sent to admin (timelock)
     * @param token The address of the ERC-20 token to sweep
     */
    function sweepToken(EIP20NonStandardInterface token) virtual override external {
        require(msg.sender == admin, "CErc20::sweepToken: only admin can sweep tokens");
        require(address(token) != underlying, "CErc20::sweepToken: can not sweep underlying token");
        uint256 balance = token.balanceOf(address(this));
        token.transfer(admin, balance);
    }
LOW severity: [PRST-5] User can lock and stake arbitrary amount of tokens without paying. **Severity:** Critical

**Path:** superfluid_lp/src/contract.rs

**Description:**

In the `superfluid_lp/src/contract.rs` contract the user has the ability to lock and stake native tokens. While staking the user has the following methods of paying for the staking:

Send the actual tokens with the...

Vulnerable code:
match &asset.info {
                AssetInfo::NativeToken { denom } => {
                    for coin in info.funds.iter() {
                        if coin.denom == *denom {
                            // validate that the amount sent is exactly equal to the amount that is expected to be locked.
                            if coin.amount != asset.amount {
                                return Err(ContractError::InvalidAmount);
                            }
                        }
                    }
                }
                AssetInfo::Token { contract_addr: _ } => {
                    return Err(ContractError::UnsupportedAssetType);
                }
            }

ExecuteMsg::LockLstAsset { asset} => {

            let user = info.sender.clone();

            // validate that the asset is allowed to be locked.
            let config = CONFIG.load(deps.storage)?;
            let mut allowed = false;
            for allowed_asset in config.allowed_lockable_tokens {
                if allowed_asset == asset.info {
                    allowed = true;
                    break;
                }
            }

            if !allowed {
                return Err(ContractError::AssetNotAllowedToBeLocked);
            }

            let mut locked_amount: Uint128 = LOCK_AMOUNT
                .may_load(deps.storage, (&user, &asset.info.to_string()))?
                .unwrap_or_default();
       
            // confirm that this asset was sent along with the message. We only support native assets.
            match &asset.info {
                AssetInfo::NativeToken { denom } => {
                    for coin in info.funds.iter() {
                        if coin.denom == *denom {
                            // validate that the amount sent is exactly equal to the amount that is expected to be locked.
                            if coin.amount != asset.amount {
                                return Err(ContractError::InvalidAmount);
                            }
                        }
                    }
                }
                AssetInfo::Token { contract_addr: _ } => {
                    return Err(ContractError::UnsupportedAssetType);
                }
            }

             // add the amount to the locked amount
            locked_amount = locked_amount + asset.amount;

            // update locked amount
            LOCK_AMOUNT.save(deps.storage, (&user, &asset.info.to_string()), &locked_amount)?;
            Ok(Response::default())
        }
LOW severity: [PRST-1] Division by zero in share conversion leads to panic and denial-of-service. **Severity:** High

**Path:** x/liquidstake/types/liquidstake.go:NativeTokenToStkXPRT, StkXPRTToNativeToken#L167-L175

**Description:**

Both functions `NativeTokenToStkXPRT` and `StkXPRTToNativeToken` are used to convert `xprt` to `stkxprt` and vice versa. The conversion is based on the total net.....

Vulnerable code:
// NativeTokenToStkXPRT returns StkxprtTotalSupply * nativeTokenAmount / netAmount
func NativeTokenToStkXPRT(nativeTokenAmount, stkXPRTTotalSupplyAmount math.Int, netAmount math.LegacyDec) (stkXPRTAmount math.Int) {
	return math.LegacyNewDecFromInt(stkXPRTTotalSupplyAmount).MulTruncate(math.LegacyNewDecFromInt(nativeTokenAmount)).QuoTruncate(netAmount.TruncateDec()).TruncateInt()
}

// StkXPRTToNativeToken returns stkXPRTAmount * netAmount / StkxprtTotalSupply with truncations
func StkXPRTToNativeToken(stkXPRTAmount, stkXPRTTotalSupplyAmount math.Int, netAmount math.LegacyDec) (nativeTokenAmount math.LegacyDec) {
	return math.LegacyNewDecFromInt(stkXPRTAmount).MulTruncate(netAmount).Quo(math.LegacyNewDecFromInt(stkXPRTTotalSupplyAmount)).TruncateDec()
}
HIGH severity: [PRST-6] First depositor can steal assets due to missing slippage protection. **Severity:** High

**Path:** x/liquidstake/keeper/liquidstake.go:LiquidStake

**Description:**

The function to mint `stkXPRT` does not have any slippage protection. It only uses the input amount in `xprt`, but the output amount of minted shares is not checked, such as against a minimum amount...

Vulnerable code:
func (k Keeper) LiquidStake(
	ctx sdk.Context, proxyAcc, liquidStaker sdk.AccAddress, stakingCoin sdk.Coin) (newShares math.LegacyDec, stkXPRTMintAmount math.Int, err error) {
    [..]
    nas := k.GetNetAmountState(ctx)
    [..]
	// mint stkxprt, MintAmount = TotalSupply * StakeAmount/NetAmount
	liquidBondDenom := k.LiquidBondDenom(ctx)
	stkXPRTMintAmount = stakingCoin.Amount
	if nas.StkxprtTotalSupply.IsPositive() {
		stkXPRTMintAmount = types.NativeTokenToStkXPRT(stakingCoin.Amount, nas.StkxprtTotalSupply, nas.NetAmount)
	}
	[..]
}

func (k Keeper) GetNetAmountState(ctx sdk.Context) (nas types.NetAmountState) {
	totalRemainingRewards, totalDelShares, totalLiquidTokens := k.CheckDelegationStates(ctx, types.LiquidStakeProxyAcc)

	totalUnbondingBalance := sdk.ZeroInt()
	ubds := k.stakingKeeper.GetAllUnbondingDelegations(ctx, types.LiquidStakeProxyAcc)
	for _, ubd := range ubds {
		for _, entry := range ubd.Entries {
			// use Balance(slashing applied) not InitialBalance(without slashing)
			totalUnbondingBalance = totalUnbondingBalance.Add(entry.Balance)
		}
	}

	nas = types.NetAmountState{
		StkxprtTotalSupply:    k.bankKeeper.GetSupply(ctx, k.LiquidBondDenom(ctx)).Amount,
		TotalDelShares:        totalDelShares,
		TotalLiquidTokens:     totalLiquidTokens,
		TotalRemainingRewards: totalRemainingRewards,
		TotalUnbondingBalance: totalUnbondingBalance,
		ProxyAccBalance:       k.GetProxyAccBalance(ctx, types.LiquidStakeProxyAcc).Amount,
	}

	nas.NetAmount = nas.CalcNetAmount()
	nas.MintRate = nas.CalcMintRate()
	return
}

func NativeTokenToStkXPRT(nativeTokenAmount, stkXPRTTotalSupplyAmount math.Int, netAmount math.LegacyDec) (stkXPRTAmount math.Int) {
	return math.LegacyNewDecFromInt(stkXPRTTotalSupplyAmount).MulTruncate(math.LegacyNewDecFromInt(nativeTokenAmount)).QuoTruncate(netAmount.TruncateDec()).TruncateInt()
}
LOW severity: [PRST-3] Whitelisted validators cannot be inactivated. **Severity:** Low

**Path:** x/liquidstake/types/params.go:validateWhitelistedValidators#L101-L126

**Description:**

According to the documentation of a `WhitelistedValidator`, a validator is deemed inactive if their target weight is set to zero. This can be seen in...

Vulnerable code:
// WhitelistedValidator consists of the validator operator address and the
// target weight, which is a value for calculating the real weight to be derived
// according to the active status. In the case of inactive, it is calculated as
// zero.

if !wv.TargetWeight.IsPositive() {
	return fmt.Errorf("liquidstake validator target weight must be positive: %s", wv.TargetWeight)
}

func validateWhitelistedValidators(i interface{}) error { 
	wvs, ok := i.([]WhitelistedValidator)
	if !ok {
		return fmt.Errorf("invalid parameter type: %T", i)
	}
	valsMap := map[string]struct{}{}
	for _, wv := range wvs {
		_, valErr := sdk.ValAddressFromBech32(wv.ValidatorAddress)
		if valErr != nil {
			return valErr
		}

		if wv.TargetWeight.IsNil() {
			return fmt.Errorf("liquidstake validator target weight must not be nil")
		}
		if !wv.TargetWeight.IsPositive() {
			return fmt.Errorf("liquidstake validator target weight must be positive: %s", wv.TargetWeight)
		}

		if _, ok := valsMap[wv.ValidatorAddress]; ok {
			return fmt.Errorf("liquidstake validator cannot be duplicated: %s", wv.ValidatorAddress)
		}
		valsMap[wv.ValidatorAddress] = struct{}{}
	}
	return nil
}
LOW severity: [FUE13] Decimal precision oversight in cross-layer token transactions. **Severity:** High

**Path:** fuel-bridge/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol, fuel-bridge/packages/fungible-token/bridge-fungible-token/src/main.sw

**Description:**

The sway bridge contract uses the same number of decimals for all...

Vulnerable code:
const DEFAULT_DECIMALS: u8 = 9u8;

configurable {
    DECIMALS: u64 = 9u64,
    BRIDGED_TOKEN_GATEWAY: b256 = 0x00000000000000000000000096c53cd98B7297564716a8f2E1de2C83928Af2fe,
}

impl SRC20 for Contract {
    ...

    #[storage(read)]
    fn decimals(asset: AssetId) -> Option<u8> {
        match storage.tokens_minted.get(asset).try_read() {
            Some(_) => Some(DECIMALS.try_as_u8().unwrap_or(DEFAULT_DECIMALS)),
            None => None,
        }
    }
}

let sub_id = _generate_sub_id_from_metadata(message_data.token_address, message_data.token_id);
HIGH severity: [FUE19] FuelERGatewayV4.sendMetadata() produces a corrupted message to the Portal because of the missed type conversion. **Severity:** Medium

**Path:** FuelERC20GatewayV4.sol#L190-L199, src/data_structures/message_data.sw#L21-L30 

**Description:**

The `FuelERC20GatewayV4.sendMetadata()` function encodes the `MessageType` enum using uint8 instead of uint256, as you can see in the code snippet below.

As a result,...

Vulnerable code:
13acd573529992578e7f0fdbe54c747a8a9d91f757947b56d343741d40a0c79103000000000000000000000000e7f1725e7734ce288f8367e1bb143e90bb3f05120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000003544b4e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005546f6b656e000000000000000000000000000000000000000000000000000000

bytes memory metadataMessage = abi.encodePacked(
            assetIssuerId,
            MessageType.METADATA,
            abi.encode(
                tokenAddress,
                uint256(0), // token_id = 0 for all erc20 deposits
                IERC20MetadataUpgradeable(tokenAddress).symbol(),
                IERC20MetadataUpgradeable(tokenAddress).name()
            )
        );

pub fn parse(msg_idx: u64) -> Self {
        let message_type: u8 = input_message_data(msg_idx, OFFSET_MESSAGE_TYPE).get(31).unwrap(); // Get the last byte

        match message_type {
            DEPOSIT => MessageData::Deposit(DepositMessage::parse_deposit_to_address(msg_idx)),
            CONTRACT_DEPOSIT => MessageData::Deposit(DepositMessage::parse_deposit_to_contract(msg_idx)),
            CONTRACT_DEPOSIT_WITH_DATA => MessageData::Deposit(DepositMessage::parse_deposit_to_contract_with_data(msg_idx)),
            METADATA => MessageData::Metadata(MetadataMessage::parse(msg_idx)),
            _ => revert(0),
        }
    }
HIGH severity: [FUE16] Discrepancies between FuelERGateway and DepositMessage. **Severity:** Medium

**Path:** fuel-bridge/packages/fungible-token/bridge-fungible-token/src/data_structures/deposit_message.sw

**Description:**

In the `depositWithData()` function of the `FuelERC20GatewayV4` solidity contract, the message `data` is constructed with the arbitrary data as the...

Vulnerable code:
bytes memory depositMessage = abi.encodePacked(
    assetIssuerId,
    uint256(data.length == 0 ? MessageType.DEPOSIT_TO_CONTRACT : MessageType.DEPOSIT_WITH_DATA),
    bytes32(uint256(uint160(tokenAddress))),
    uint256(0), // token_id = 0 for all erc20 deposits
    bytes32(uint256(uint160(msg.sender))),
    to,
    l2MintedAmount,
    data
);

pub fn parse_deposit_to_contract_with_data(msg_idx: u64) -> Self {
    Self {
        amount: input_message_data(msg_idx, OFFSET_AMOUNT).into(),
        from: input_message_data(msg_idx, OFFSET_FROM).into(),
        token_address: input_message_data(msg_idx, OFFSET_TOKEN_ADDRESS).into(),
        to: Identity::ContractId(ContractId::from(b256::from(input_message_data(msg_idx, OFFSET_TO)))),
        token_id: input_message_data(msg_idx, OFFSET_TOKEN_ID).into(),
        decimals: input_message_data(msg_idx, OFFSET_DECIMALS).get(31).unwrap(),
        deposit_type: DepositType::ContractWithData,
    }
}
LOW severity: [FUE21] Failure to transfer the base asset to the recipient. **Severity:** Medium

**Path:** fuel-bridge/packages/message-predicates/contract-message-predicate/script_asm.rs#L54, fuel-bridge/packages/fungible-token/bridge-fungible-token/src/main.sw#L336-L348

**Description:**

The transaction script `script_asm.rs` facilitates the transfer of the base asset.....

Vulnerable code:
op::call(REG_DATA_PTR, REG_MSG_AMOUNT, REG_ASSET_PTR, RegId::CGAS),

op::gtf(
    REG_MSG_AMOUNT,
    RegId::ZERO,
    GTFArgs::InputMessageAmount.into(),
)

let asset_id = AssetId::new(contract_id(), sub_id);
HIGH severity: [FUE5] Use safeTransferFrom instead of transferFrom for ERtransfers. **Severity:** Medium

**Path:** fuel-bridge/packages/solidity-contracts/contracts/messaging/gateway/FuelERC721Gateway.sol::finalizeWithdrawal()#L168-L182

**Description:**

As per [OpenZeppelin’s documentation](https://docs.openzeppelin.com/contracts/4.x/api/token/erc721#IERC721-transferFrom-address...

Vulnerable code:
IERC721Upgradeable(tokenAddress).transferFrom(address(this), to, tokenId);

function finalizeWithdrawal(
    address to,
    address tokenAddress,
    uint256 /*amount*/,
    uint256 tokenId
) external payable override whenNotPaused onlyFromPortal {
    bytes32 fuelContractId = messageSender();
    require(_deposits[tokenAddress][tokenId] == fuelContractId, "Fuel bridge does not own this token");

    delete _deposits[tokenAddress][tokenId];

    IERC721Upgradeable(tokenAddress).transferFrom(address(this), to, tokenId);
    //emit event for successful token withdraw
    emit Withdrawal(bytes32(uint256(uint160(to))), tokenAddress, fuelContractId, 1);
}
HIGH severity: [FUE12] Fee-on-transfer tokens are not supported by FuelERGatewayV4 and can be stolen from it. **Severity:** Medium

**Path:** fuel-bridge/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol#L268-L269

**Description:**

`FuelERC20GatewayV4` does not support fee-on-transfer tokens because accounting doesn’t check the difference between the...

Vulnerable code:
//transfer tokens to this contract and update deposit balance
        IERC20MetadataUpgradeable(tokenAddress).safeTransferFrom(msg.sender, address(this), amount);

uint256 balanceBefore = IERC20(tokenAddress).balanceOf(address(this));
IERC20MetadataUpgradeable(tokenAddress).safeTransferFrom(msg.sender, address(this), amount);
uint256 balanceAfter = IERC20(tokenAddress).balanceOf(address(this));

uint256 toMint = balanceAfter - balanceBefore;