#49527 [SC-Low] Edge case Integer UInt64SetLib.py::remove_item leads to int underflow

Submitted on Jul 16th 2025 at 21:18:01 UTC by @hunter0xweb3 for Audit Comp | Folks Smart Contract Library

  • Report ID: #49527

  • Report Type: Smart Contract

  • Report severity: Low

  • Target: https://github.com/Folks-Finance/algorand-smart-contract-library/blob/main/contracts/library/UInt64SetLib.py

  • Impacts:

    • Contract fails to deliver promised returns, but doesn't lose value

Description

Brief/Intro

UInt64SetLib.py::remove_item doesnt checks that items parameter is not empty, leading to int underflow when an empty items set is used as a parameter removing an element from an empty set should return an empty set , it should not revert, this breaks the promised return of calling remove_item from a set returns a set

Vulnerability Details

UInt64SetLib.py::remove_item edge case leads to int underflow when calling remove_item with an empty set. The vulnerability occurs because function declares the variable last_idx to be equal to array length - 1: https://github.com/Folks-Finance/algorand-smart-contract-library/blob/7673a43fa5183af736b65f17d1a297fdea672059/contracts/library/UInt64SetLib.py#L32-L41

def remove_item(to_remove: UInt64, items: DynamicArray[ARC4UInt64]) -> Tuple[Bool, DynamicArray[ARC4UInt64]]:
@>    last_idx = items.length - 1

However if empty array is passed this leads to a frame dig -1 and call to be reverted, instead of returning an empty array This is significative because call parameters to this functions are dynamic and no data integrity assumptions should be made, so defense in depth should be enforced

Impact Details

  • Function remove_item doesnt applies security in depth mechanism

  • remove_item is integrity inconsistent, removing an element from an empty set should return an empty set , it should not revert, this breaks the promised return of calling remove_item from a set returns a set

References

https://github.com/Folks-Finance/algorand-smart-contract-library/blob/7673a43fa5183af736b65f17d1a297fdea672059/contracts/library/UInt64SetLib.py#L32-L41

Proof of Concept

Proof of Concept

Create the following test case in tests/library/UInt64SetLib.test.ts inside remove item section:

    test("Removing from empty arrays reverts instead of returning empty array ", async () => {
      const items = [];
      expect(await client.removeItem({ args: [0n, items] })).toEqual([false, items]);
    });

Observe function remove_item reverts but it should return an empty array

  ● UInt64SetLib › remove item › Removing from empty arrays reverts instead of returning empty array 

        frame_dig -1

          intc_0 // 0
          extract_uint16
          dup
          intc_1 // 1
          - <--- Error
          intc_0 // 0
      remove_item_for_header@1:
          // contracts/library/UInt64SetLib.py:34

Was this helpful?