57940 sc medium deterministic address collision in cairo deployment causes dos and unintended receiver sharing

Submitted on Oct 29th 2025 at 14:36:25 UTC by @Kissiahmyo for Audit Comp | Belongarrow-up-right

  • Report ID: #57940

  • Report Type: Smart Contract

  • Report severity: Medium

  • Target: https://github.com/immunefi-team/audit-comp-belong/blob/feat/cairo/src/nftfactory/nftfactory.cairohttps://github.com/immunefi-team/audit-comp-belong/blob/feat/cairo/src/nftfactory/nftfactory.cairo

  • Impacts:

    • Block stuffing

Description

Brief/Intro

The Cairo NFTFactory deploys receiver.cairo and nft.cairo contracts with deploy_syscall using salt = 0 and fixed constructor calldata. StarkNet computes addresses deterministically from class_hash, salt, constructor_calldata, deploy_from_zero, and caller address. For identical inputs, the derived address is the same. A second deployment to an already‑used address triggers an error that .unwrap_syscall() turns into a revert, producing denial of service for per‑collection receivers and unintentionally coupling multiple collections to one shared receiver. If royalty_fraction = 0, referral usage is recorded but no receiver is deployed, leaving referral payout logic absent.

Vulnerability Details

Using deploy_syscall with salt = 0 and fixed constructor parameters derives a deterministic address. Repeated productions under the same (creator, referral_code, platform, factory) collide, causing unwrap_syscall() to revert and forcing a single shared receiver across distinct NFT collections. This can block per‑collection receivers and break accounting isolation.

Relevant code (from src/nftfactory/nftfactory.cairo:_produce):

            let mut receiver_address: ContractAddress = contract_address_const::<0>();
            self._set_referral_user(info.referral_code, get_caller_address());
            if info.royalty_fraction.is_non_zero() {
                let referral_creator = self._get_referral_creator(info.referral_code);
                let receiver_constructor_calldata: Array<felt252> = array![
                    info.referral_code,
                    get_caller_address().into(),
                    self.factory_parameters.platform_address.read().into(),
                    referral_creator.into(),
                ];

                let (address, _) = deploy_syscall(
                    self.receiver_class_hash.read(), 0, receiver_constructor_calldata.span(), false,
                )
                    .unwrap_syscall();
                receiver_address = address;
            }iro

Why collision occurs:

  • StarkNet address derivation uses five inputs: class_hash, salt, constructor_calldata, deploy_from_zero, and caller address when deploy_from_zero = false.

  • Keeping salt = 0, constructor calldata fixed to [referral_code, creator, platform_address, referral_creator], and factory caller constant derives the same address across productions. A second deployment attempt hits an existing address and reverts at .unwrap_syscall().

Contrast with Solidity V2:

  • Solidity uses unique salts derived from (name, symbol) and validates address prediction:

    • Business‑level uniqueness: require(getNftInstanceInfo[hashedSalt].nftAddress == address(0), TokenAlreadyExists())

    • Deterministic clone and validation: cloneDeterministic(hashedSalt) with predictDeterministicAddress(...) then require(predicted == actual, RoyaltiesReceiverAddressMismatch())

  • Outcome: distinct (name, symbol) collections receive distinct RoyaltiesReceiverV2 instances, avoiding collisions and ensuring per‑collection isolation.

Impact Details

  • Denial of Service (DoS) on production:

    • Attempting to produce multiple collections with the same (creator, referral_code, platform, factory) and royalty_fraction > 0 reuses the receiver’s deterministic address; a second deployment reverts, blocking production.

    • Operational disruption: teams cannot deploy a new per‑collection receiver when needed; CI or scripts looping production may repeatedly fail.

  • Unintended receiver sharing across collections:

    • Distinct collections inadvertently share one receiver instance, breaking desired per‑collection isolation, access control, upgradability independence, and auditability.

  • Accounting inconsistencies and fee routing issues:

    • Referral usage is recorded in the factory even when royalty_fraction = 0, but no receiver exists to handle referral splits for secondary royalties; without a clear payout path, referral share accounting can be missing or inconsistent.

    • Global referral “times used” increments: producing new collections can elevate the referral tier globally, unintentionally increasing referral share for all existing and future collections under the same (creator, code) context. This can divert platform fees unexpectedly.

References

  • Cairo source:

    • src/nftfactory/nftfactory.cairo (receiver deploy and referral usage)

      • _set_referral_user(...) and receiver deploy

      • NFT deploy

      • Referral usage logic

    • src/receiver/receiver.cairo: payout shares and referral split handling

Links in report:

Proof of Concept

chevron-rightPOC: demonstrate deterministic receiver address collision under identical (creator, referral_code, platform, factory)hashtag

Test output:

Was this helpful?