#48983 [SC-Low] Potential Underflow in remove_item() on Empty Array

Submitted on Jul 10th 2025 at 08:05:30 UTC by @Oxenzo_eth for Audit Comp | Folks Smart Contract Library

  • Report ID: #48983

  • 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

When calling remove_item(to_remove, items) on an empty DynamicArray, the code computes last_idx = items.length - 1, which underflows the unsigned integer. Although the function still returns (False, items.copy()), this underflow can introduce unwanted behavior and should be addressed to maintain clarity and correctness.

Vulnerability Details

The remove_item function which is here:

@subroutine
def remove_item(to_remove: UInt64, items: DynamicArray[ARC4UInt64]) -> Tuple[Bool, DynamicArray[ARC4UInt64]]:
    last_idx = items.length - 1
    for idx, item in uenumerate(items):
        if item.native == to_remove:
            # remove last item to replace the "to_remove" item or remove entirely if it's the match
            last_item = items.pop()
            if idx != last_idx:
                items[idx] = last_item
            # return with the item removed
            return Bool(True), items.copy()

    # if here then item is not present
    return Bool(False), items.copy()

begins by calculating -> last_idx = items.length - 1

For an empty array, items.length == 0, so last_idx wraps to the maximum UInt64 value (e.g., 2**64 - 1), due to unsigned underflow. Although the subsequent loop does not fire and the code returns safely, the presence of an invalid last_idx may:

Consume TEAL stack unnecessarily or increase code complexity.

# Potential underflow scenario
items = DynamicArray([])
last_idx = items.length - 1  # underflows -> UInt64 max
for idx, item in uenumerate(items):  # loop skipped
    ...
return Bool(False), items.copy()

Impact Details

Contract fails to deliver promised behavior

References

The function can be located here: https://github.com/Folks-Finance/algorand-smart-contract-library/blob/7673a43fa5183af736b65f17d1a297fdea672059/contracts/library/UInt64SetLib.py#L32

Proof of Concept

Proof of Concept

Kindly place this in the UInt64SetLib.test.ts

describe("remove_item underflow behavior", () => {
  test("calling removeItem on empty items array should return false and empty array", async () => {
    const { appClient: client } = await factory.deploy();

    await localnet.algorand.send.payment({
      sender: creator,
      receiver: getApplicationAddress(client.appId),
      amount: (1).algo(),
    });

    // Confirm empty array behavior
    const result = await client.removeItem({ args: [0n, []] });

    expect(result).toEqual([false, []]);
  });
});

Was this helpful?