CoinFabrik/scout-substrate huggingface dataset:
Audit finding: XCM instructions can charge 0 weight
Project: MantaNetwork
Auditor: Veridise
Location: runtime/calamari/src/weights/xcm/mod.rs
runtime/dolphin/src/weights/xcm/mod.rs
Impact: Low
Vulnerability class (audit): Bad Extrinsic Weight
Vulnerability class (Scout): Weight Management
Status: Acknowledged

4.1.12 V-MANC-VUL-012: XCM instructions can charge 0 weight

Severity: Low
Type: Bad Extrinsic Weight
Commit: 45ba60e1d
Status: Acknowledged

File(s): runtime/(calamari, dolphin)/src/weights/xcm/mod.rs
Location(s): Every use of weigh_multi_assets

The Polkadot ecosystem uses the XCM messaging standard to enable parachains and the relay chain to communicate with each other. For example, if a parachain P1 wants to deposit an asset onto another parachain P2, they can construct an XCM message stating they wish to deposit an asset into an account associated with P1 and send it to P2.

Each XCM message consists of a sequence of low-level XCM instructions that get executed by the XCM executor on the destination parachain. To offset the cost of executing these instructions, parachains set weights for each instruction, so the sender of the XCM message is charged fees for the destination parachain executing their message.

Manta Chain configured the weights of multiple instructions so that senders could generate messages with a total weight of 0. For example, in the code snippet below, the function deposit_asset sets the weight for the XCM instruction deposit_asset based on a parameter called assets. When assets is an empty vector, the weight_multi_assets function returns 0, resulting in a total weight of 0 for the instruction:

fn deposit_asset(
    assets: &MultiAssetFilter,
    _max_assets: &u32,
    _dest: &MultiLocation,
) -> Weight {
    let hardcoded_weight: u64 = 1_000_000_000;
    let weight = assets.weigh_multi_assets(XcmFungibleWeight::<Runtime>::deposit_asset());
    cmp::min(hardcoded_weight, weight)
}

This setup allows malicious or incompetent senders to spam Manta with messages costing 0, even though the instruction is successfully executed by the XCM executor. While a denial of service may be unlikely due to the fast execution of 0-length vectors, spam prevention is recommended. Adding a minimal base fee for instructions that can be executed with 0 weight would help prevent spam.

Impact: Malicious users may spam Manta with XCM messages of weight 0, potentially slowing down blockchain performance and risking denial of service.

Recommendation: We recommend always charging a base fee to prevent spam.

Developer Response: The developers acknowledged the issue and plan to change weigh_multi_assets to charge the benchmarked weight of a single asset execution for any successfully executed multiasset XCM message.

Summary: XCM instructions can be executed with zero weight, allowing potential spam through 0-cost messages.
Audit finding: Missing validation in set_units_per_second
Project: MantaNetwork
Auditor: Veridise
Location: pallets/asset-manager/src/lib.rs
Impact: Low
Vulnerability class (audit): Data Validation
Vulnerability class (Scout): Error Handling and Validation
Status: Acknowledged

4.1.13 V-MANC-VUL-013: Missing validation in set_units_per_second

Severity: Low
Type: Data Validation
Commit: 45ba60e1d
Status: Acknowledged

File(s): pallets/asset-manager/src/lib.rs
Location(s): set_units_per_second

The asset-manager pallet manages a hashmap called UnitsPerSecond which maps assetIds to a u128 value units_per_second, used to determine the price for an XCM transfer. It provides a function, set_units_per_second, to set the units_per_second for a given asset. This value determines the cost (in the corresponding asset) to purchase a certain weight for a transaction. The following code snippet calculates the amount:

let units_per_second = M::units_per_second(&asset_id).ok_or({
    log::debug!(
        target: "FirstAssetTrader::buy_weight",
        "units_per_second missing for asset with id: {:?}",
        id,
    );
    XcmError::TooExpensive
})?;

let amount = units_per_second * (weight as u128) / (WEIGHT_PER_SECOND as u128);

if amount.is_zero() {
    return Ok(payment);
}

This calculation of amount uses multiplication, which can overflow. Currently, both units_per_second and amount are of type u128. If units_per_second exceeds u128::MAX / u64::MAX, a large weight (u64::MAX) can be bought for a small asset amount, allowing a malicious parachain to potentially perform a DoS attack.

There is currently no validation in set_units_per_second to ensure units_per_second is small enough. Although only the root can call set_units_per_second, there is a risk if the root user mistakenly sets an excessive value or is tricked into doing so.

Impact: If units_per_second is set above u128::MAX / u64::MAX, large amounts of weight can be purchased at low cost, potentially enabling a DoS attack on the chain.

Recommendation: Change the type of units_per_second to map assetId to a u64 value or validate that the amount is sufficiently small.

Developer Response: The developers acknowledged the issue and plan to fix the weight calculation to use saturating arithmetic, which should address this vulnerability.

Summary: Missing validation in set_units_per_second allows potential overflow, enabling weight to be purchased at low cost, risking DoS.
Audit finding: Collator is a single point of failure for a round
Project: MantaNetwork
Auditor: Veridise
Impact: Low
Vulnerability class (audit): Consensus
Vulnerability class (Scout): Code Quality
Status: Acknowledged

4.1.14 V-MANC-VUL-014: Collator is a single point of failure for a round

Severity: Low
Type: Consensus
Commit: 45ba60e1d
Status: Acknowledged

The Manta parachain uses the Aura consensus mechanism to select collators to author blocks. Aura selects a primary collator for a round, and only that collator is allowed to produce blocks in that round. However, if that collator goes down, then no blocks will be produced, making that collator a single point of failure.

Other parachains, like Moonbeam, address this by selecting multiple collators for a given round.

Impact: If a collator goes down, no blocks will be produced for that round, thereby impacting the transaction throughput of Manta.

Recommendation: We recommend that Manta adopt a consensus mechanism that selects multiple collators, ideally geographically separated, so if one collator fails, the likelihood of others failing remains low.

Developer Response: The developers are aware of this issue and have plans to move away from the Aura consensus mechanism.

Summary: Single collator selection creates a point of failure, risking block production halt if the collator goes down.
Audit finding: Unchecked index calculation in spend_all
Project: MantaNetwork
Auditor: Veridise
Location: pallets/manta-pay/src/lib.rs
Impact: Warning
Vulnerability class (audit): Logic Error
Vulnerability class (Scout): Arithmetic
Status: Acknowledged

4.1.15 V-MANC-VUL-015: Unchecked index calculation in spend_all

Severity: Warning
Type: Logic Error
Commit: 45ba60e1d
Status: Acknowledged

File(s): pallets/manta-pay/src/lib.rs
Location(s): spend_all

The spend_all function in the Manta-Pay pallet does the following:
1. Adds the nullifier commitments in the TransactionPost to the NullifierCommitmentSet.
2. Inserts each (nullifier, outgoingNote) pair into the NullifierSetInsertionOrder structure.
3. Updates a global variable, NullifierSetSize, which stores the size of the nullifier commitment set.

The index where the pair gets inserted, along with the new nullifier size, is calculated as index + i, where i is the index of the corresponding SenderPost, and index is the current size of the set. However, this arithmetic is unchecked and could result in an overflow.

Impact: When the size of the commitment set reaches u64::MAX, the index calculation may overflow, causing the pair to be inserted at the beginning of the list and setting the nullifier set size to 1. However, reaching this value through normal execution is extremely unlikely.

Recommendation: Add an overflow check and return an error.

Developer Response: TBD.

Summary: Unchecked index calculation in spend_all may cause overflow, leading to incorrect data insertion.
Audit finding: Excess fees not refunded
Project: MantaNetwork
Auditor: Veridise
Location: parachain-staking/lib.rs
Impact: Warning
Vulnerability class (audit): Bad Extrinsic Weight
Vulnerability class (Scout): Weight Management
Status: Intended Behavior

4.1.16 V-MANC-VUL-016: Excess fees not refunded

Severity: Warning
Type: Bad Extrinsic Weight
File(s): parachain-staking/lib.rs
Location(s): (cancel_leave, execute_leave, schedule_leave, join)_candidates
Commit: 45ba60e1d
Status: Intended Behavior

When a substrate extrinsic is created, its weight must be carefully considered to ensure it correctly reflects the computational cost of the operation as extrinsic weight is directly related to the fees that are charged to the user. This weight should capture the maximum number of computational resources that will be consumed by the extrinsic as excess fees can be returned. In several functions, though, the weights are computed based on the value of an argument provided by the user which might not always reflect the true cost of the computation.

For example, consider the following:

1#[pallet::call_index(11)]
2#[pallet::weight(<T as Config>::WeightInfo::cancel_leave_candidates(*candidate_count))]
3/// Cancel open request to leave candidates
4/// - only callable by collator account
5/// - result upon successful call is the candidate is active in the candidate pool
pub fn cancel_leave_candidates(
7origin: OriginFor<T>,
8#[pallet::compact] candidate_count: u32,
) -> DispatchResultWithPostInfo {
...
12let mut candidates = <CandidatePool<T>>::get();
13ensure!(
14candidates.0.len() as u32 <= candidate_count,
15Error::<T>::TooLowCandidateCountWeightHintCancelLeaveCandidates
);
...
18Ok(().into())
}

In this function, the weight is computed using the candidate_count argument, and in order for the function to execute successfully, candidate_count must be greater than or equal to the current size of the candidate pool. A user might need to call this function with a candidate_count that is larger than the size of the pool to prevent a front-running attack where a malicious user would add candidates to prevent the transaction from executing successfully. In such a case, the weight would be larger than necessary, but no fees are returned to the user.

Impact: Such functions can charge unnecessary fees to the user.

Recommendation: Refund the user additional fees that are not consumed.

Developer Response: Since these extrinsics are likely to be executed sparingly and since the additional fees are likely to be small, we feel like the additional computational cost of determining the excess does not.

Summary: Extrinsics may charge unnecessary fees without refunding excess.
Audit finding: Assets can be registered at unsupported locations
Project: MantaNetwork
Auditor: Veridise
Location: pallets/asset-manager/src/lib.rs
Impact: Warning
Vulnerability class (audit): Data Validation
Vulnerability class (Scout): Error Handling and Validation
Status: Acknowledged

4.1.17 V-MANC-VUL-017: Assets can be registered at unsupported locations

Severity: Warning
Type: Data Validation
File(s): pallets/asset-manager/src/lib.rs
Location(s): register_asset
Commit: 45ba60e1d
Status: Acknowledged

The asset-manager pallet allows assets to be registered, managed, and minted. In particular, register_asset takes as input an asset, location, and corresponding asset_metadata and registers the asset. Every asset must be associated with a location; however, Manta only supports assets from specific locations. The current implementation of asset-manager does not perform any validation on the locations passed into register_asset, potentially allowing assets to be registered from untested locations. The pallet also exposes a method called update_asset_location, which is supposed to update the location of an asset. It similarly does not perform any validation on the new location of the asset.

Impact: The current implementation allows assets to be registered from untested locations.

Recommendation: The asset-manager pallet already implements the Contains trait, which exposes a method contains that takes as input a location and returns true if and only if the location is supported. Currently, that method is unused and can be used to validate the locations passed in.

Developer Response: The developers acknowledged the issue and plan to add a check in register_asset.

Summary: Assets can be registered without validating their location.
Audit finding: Minimum delegator funds is not MinDelegatorStk
Project: MantaNetwork
Auditor: Veridise
Location: parachain-staking/lib.rs
Impact: Warning
Vulnerability class (audit): Logic Error
Vulnerability class (Scout): Business Logic
Status: Acknowledged

4.1.18 V-MANC-VUL-018: Minimum delegator funds is not MinDelegatorStk

Severity: Warning
Type: Logic Error
File(s): parachain-staking/lib.rs
Location(s): N/A
Commit: 45ba60e1d
Status: Acknowledged

In the case where MinDelegation < MinDelegatorStk, it is possible for the delegator’s staked funds to be less than MinDelegatorStk. This can occur through the following sequence of calls:

1. delegate amount N from delegator D to candidate C1 where N >= MinDelegatorStk
2. delegate amount M from delegator D to candidate C2 where M < MinDelegatorStk and M >= MinDelegation
3. schedule_leave_candidates and execute_leave_candidates for C1

This results in D having M funds staked, where M < MinDelegatorStk.

Impact: If MinDelegation is less than MinDelegatorStk, a delegator may end up with less than MinDelegatorStk funds actually staked.

Note: This is not currently exploitable because MinDelegation == MinDelegatorStk in all production runtimes. However, if these values are adjusted in the future, this bug may become exploitable.

Recommendation: There are two options:

1. When starting a runtime, ensure that MinDelegation >= MinDelegatorStk
2. Whenever a delegation is removed (such as in execute_leave_candidates), ensure that the remaining locked funds for the delegator are at least MinDelegatorStk.

Developer Response: The developers acknowledged the issue and are considering removing MinDelegation as there is no apparent reason for it being different from MinDelegatorStk.

Summary: Delegators can have staked funds below the required MinDelegatorStk.
Audit finding: Unintended test crashes
Project: MantaNetwork
Auditor: Veridise
Location: pallets/manta-pay/src/lib.rs
Impact: Info
Vulnerability class (audit): Maintainability
Vulnerability class (Scout): Code Quality
Status: Open

4.1.19 V-MANC-VUL-019: Unintended test crashes

Severity: Info
Type: Maintainability
File(s): pallets/manta-pay/src/lib.rs
Location(s): to_private_should_work
Commit: 45ba60e1d
Status: Open

Many of the manta-pay tests randomly generate an asset id, total supply, and an amount to make private. To ensure the total supply of the asset is greater than the minimum balance, the minimum balance is always added to the randomly generated total supply, as seen in this test:

fn to_private_should_work() {
  let mut rng = OsRng;
  for _ in 0..RANDOMIZED_TESTS_ITERATIONS {
    new_test_ext().execute_with(|| {
      let asset_id = rng.gen();
      let total_free_supply = rng.gen();
      initialize_test(asset_id, total_free_supply + TEST_DEFAULT_ASSET_ED);
      mint_private_tokens(
        asset_id,
        &value_distribution(5, total_free_supply, &mut rng),
        &mut rng,
      );
    });
  }
}

If the random number generator generates a value for the total_free_supply which is greater than u128::MAX - TEST_DEFAULT_ASSET_ED, then the test will fail even though it is expected to succeed.

Impact: May cause tests to fail when they are expected to succeed.

Recommendation: Change the test to generate a value for total_free_supply between [0, u128::MAX - TEST_DEFAULT_ASSET_ED).

Developer Response: TBD

Summary: Randomized test may fail due to unintended overflow in total supply generation.
Audit finding: Loss of Reserved SBT IDs
Project: MantaNetwork
Auditor: Halborn
Location: pallets/manta-sbt/src/lib.rs:376
Impact: Low
Vulnerability class (Scout): Business Logic

4.1 (HAL-01) LOSS OF RESERVED SBT IDS - LOW (2.5)\nDescription:\nCode Location:\nThe reserve_sbt function calculates a range of IDs and stores this range in the ReservedIds storage map, using the caller’s address as the key. It was identified that users lose their reserved SBT IDs when they call the reserve_sbt function without first minting their previously reserved SBT IDs. This occurs because the previous reserved range is overwritten.\nBody of the reserve_sbt function:\nListing 1: pallets/manta-sbt/src/lib.rs (Line 376)\n368 let asset_id_range : Vec < StandardAssetId > = (0.. T :: MintsPerReserve :: get () )\n369 . map (| _ | Self :: next_sbt_id_and_increment () )\n370 . collect :: < Result < Vec < StandardAssetId > , _ > >() ?;\n\n372 // The range of ` AssetIds ` that are reserved as SBTs\n373 let start_id : StandardAssetId = * asset_id_range . first () . ok_or ( Error :: <T >:: ZeroMints ) ?;\n374 let stop_id : StandardAssetId = * asset_id_range . last () . ok_or ( Error :: <T >:: ZeroMints ) ?;\n375 ReservedIds :: <T >:: insert (& who , ( start_id , stop_id ) ) ;\nBVSS:\nAO:A/AC:L/AX:L/C:N/I:N/A:N/D:L/Y:N/R:N/S:U (2.5)\nProof Of Concept:\nThis test reserves ids two times and mints one zkSBT. The first zkSBT token will not have the id 1, it will have the id 6 instead.\nListing 2: pallets/manta-sbt/src/tests.rs\n1 #[ test ]\n2 fn hal01 () {\n3 let mut rng = OsRng ;\n4 new_test_ext () . execute_with (|| {\n5 assert_ok! ( Balances :: set_balance (\n6 MockOrigin :: root () ,\n7 ALICE ,\n8 1 _000_000_000_000_000 ,\n9 0\n10 )) ;\n11 // Reserve IDs from 1 to 5\n12 assert_ok! ( MantaSBTPallet :: reserve_sbt ( MockOrigin :: signed ( ALICE ) ) ) ;\n13 // Reserve IDs from 6 to 10\n14 assert_ok! ( MantaSBTPallet :: reserve_sbt ( MockOrigin :: signed ( ALICE ) ) ) ;\n15\n16 let value = 1;\n17 let id = field_from_id ( ReservedIds :: < Test >:: get ( ALICE ) . unwrap () .0) ;\n18 let post = sample_to_private ( id , value , & mut rng ) ;\n19 assert_ok! ( MantaSBTPallet :: to_private (\n20 MockOrigin :: signed ( ALICE ) ,\n21 Box :: new ( post ) ,\n22 bvec! [0]\n23 )) ;\n\n25 // The first zkSBT minted has the id 6.\n26 assert_eq! (\n27 SbtMetadata :: < Test >:: get (6) . unwrap () . extra ,\n28 Some ( bvec! [0])\n29 );\n30 });\n31 }\nRecommendation:\nTo resolve this issue, it is recommended to restrict users from reserving additional SBT IDs if they have not minted their previously reserved IDs.

Summary: Users lose previously reserved SBT IDs if they reserve new ones without minting the old.
Audit finding: Downcasting of 64-Bit Integer
Project: MantaNetwork
Auditor: Halborn
Location: pallets/manta-sbt/src/lib.rs:783
pallets/manta-support/src/manta_pay.rs:874,1095,1096
runtime/calamari/src/migrations/staking.rs:70
Impact: Low
Vulnerability class (Scout): Arithmetic

4.3 (HAL-03) DOWNCASTING OF 64-BIT INTEGER - LOW (2.5)\nDescription:\nIt was observed that in certain circumstances, usize values are cast to types such as u8 and u32. The usize data type in the Rust programming language represents a pointer-sized unsigned integer. The actual size of usize is dependent on the platform: it’s 32 bits on a 32-bit platform and 64 bits on a 64-bit platform. Consequently, depending on the system, there could be a cast from an u64 to an u32. This implies that an attempt could be made to store a value larger than the maximum value that can be held in an u32, leading to unexpected consequences.\nCode Location:\nFINDINGS & TECH DETAILS\nUsize is casted to u8:\nListing 6: pallets/manta-sbt/src/lib.rs (Line 783)\n768 fn pull_receivers (\n769 receiver_indices : [ usize ; MerkleTreeConfiguration :: FOREST_WIDTH ],\n770 max_update_request : u64 ,\n771 ) -> ( bool , ReceiverChunk ) {\n772 let mut more_receivers = false ;\n773 let mut receivers = Vec :: new () ;\n774 let mut receivers_pulled : u64 = 0;\n775 let max_update = if max_update_request > Self :: PULL_MAX_RECEIVER_UPDATE_SIZE {\n776 Self :: PULL_MAX_RECEIVER_UPDATE_SIZE\n777 } else {\n778 max_update_request\n779 };\n781 for ( shard_index , utxo_index ) in receiver_indices . into_iter () . enumerate () {\n782 more_receivers |= Self :: pull_receivers_for_shard (\n783 shard_index as u8 ,\n784 utxo_index ,\n785 max_update ,\n786 & mut receivers ,\n787 & mut receivers_pulled ,\n788 );\n790 if receivers_pulled == max_update && more_receivers {\n791 break ;\n792 }\n794 ( more_receivers , receivers )\n795 }\nUsize is casted to u32:\nListing 7: pallets/manta-support/src/manta_pay.rs (Line 860)\n867 impl TryFrom < merkle_tree :: CurrentPath < MerkleTreeConfiguration > > for CurrentPath {\n868 type Error = Error ;\n871 fn try_from ( path : merkle_tree :: CurrentPath < MerkleTreeConfiguration >) -> Result < Self , Error > {\n872 Ok ( Self {\n873 sibling_digest : fp_encode ( path . sibling_digest )? ,\n874 leaf_index : path . inner_path . leaf_index .0 as u32 ,\n875 inner_path : path . inner_path . path . into_iter () . map ( fp_encode ) . collect :: < Result <_ , _ > >() ? ,\n881 })\n883 }\nListing 8: pallets/manta-support/src/manta_pay.rs (Lines 1095,1096)\n1091 impl From < RawCheckpoint > for Checkpoint {\n1094 Self :: new ( checkpoint . receiver_index . map (| i| i as usize ) . into () , checkpoint . sender_index as usize ,)\nListing 9: runtime/calamari/src/migrations/staking.rs (Line 70)\n70 let n_of_candidates = manta_collator_selection :: Pallet :: <T >:: candidates () . len () as u32 ;\nRecommendation:\nTo address this issue, it is recommended to check the value against the maximum value before casting.

Summary: Casting usize to smaller types may cause data loss and unexpected behavior.
Audit finding: Extrinsics charge static fees that do not account for Merkle tree updates
Project: MantaNetwork
Auditor: Veridise
Location: pallets/manta-sbt/src/lib.rs
Impact: Low
Vulnerability class (audit): Bad Extrinsic Weight
Vulnerability class (Scout): Weight Management
Status: Fixed

4.1.2 V-MSBT-VUL-002: Extrinsics charge static fees that do not account for Merkle tree updates

Severity: Low
Type: Bad Extrinsic Weight
Commit: ceb9e46
Status: Fixed
File(s): pallets/manta-sbt/src/lib.rs
Location(s): to_private, mint_sbt_eth

Transactions to_private and mint_sbt_eth take membership proofs and store UTXOs to a Merkle tree on the ledger. manta-sbt shards this Merkle tree into 256 buckets where each bucket has its own Merkle tree. Instead of storing the entire tree at each bucket, the Ledger just stores the last path added to the tree. When adding a UTXO, the Ledger first computes its corresponding bucket, then computes the new path pointing to that UTXO, and finally adds that path to the bucket.

Computing the new path should take time proportional to log(n) where n is the size of the Merkle Tree. The current benchmarking scheme only covers cases where the previous path is small i.e, at most size 1. However, if the number of transactions gets large i.e, is on the order of hundreds of millions or billions, then the size of the path can get to 24-28 (taking shards into account). If the tree grows to this size, this means each execution of the extrinsic will perform 24-28 hashes, multiplied by the number of UTXOs to be added.

The benchmarking scheme should take into account the size of the tree to make sure that the existing weights are enough to offset the computation of the new Merkle tree path.

Impact: In general, it is important to set the weights to account for both computation and storage; setting the weight too low can allow users to perform a large number of transactions with little cost. In particular, malicious users may take advantage of the low fee to launch a DOS attack.

Recommendation: We recommend that the weights be computed with a larger database that reflects the state of the chain after a year’s worth of use.

Developer Response: In progress open PR for manta-pay right now that will be extended to pallet SBT. The manta-pay PR can be found here.

Summary: Static fees for extrinsics do not account for Merkle tree updates, risking underpriced transactions.