Part 2

Zaros
PerpetualsDEXFoundrySolidity
70,000 USDC
View results
Submission Details
Severity: low
Valid

Custom Referral Code Conflict Across Engines

Summary

The referral system allows different engines (features/apps) to create the same custom referral code. When this happens, the last engine to create the code overwrites previous ones, causing referrals to credit the wrong person.

Vulnerability Details

How It Works

  1. Global Code Storage: Custom codes like "SUMMER24" are stored in one shared list (not separated by engine)

  2. No Ownership Checks: Any engine can create/modify any code, even if another engine made it first

  3. Last Write Wins: The most recent engine using a code becomes its "owner", erasing previous links

    Simple Analogy

    Imagine two stores (Engine 1 and 2) both offering "FREEPIZZA" coupons. The last store to issue the coupon becomes the only one that accepts it, confusing customers who got it from the first store.

POC (PROOF OF CONCEPT)

//SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;
import {Test} from "forge-std/Test.sol";
import {Referral} from "@zaros/referral/Referral.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
contract ReferralCustomCodeConflictPOC is Test {
Referral referral;
Referral implementation;
ERC1967Proxy proxy;
address owner = makeAddr("tanjiro");
function setUp() external {
implementation = new Referral();
bytes memory initData = abi.encodeWithSelector(Referral.initialize.selector, owner);
proxy = new ERC1967Proxy(address(implementation), initData);
referral = Referral(address(proxy));
}
function test_OwnerSetCorrectly() external view {
assertEq(referral.owner(), owner);
}
function test_customCodeConflict() public {
address engine1 = makeAddr("engine1");
address engine2 = makeAddr("engine2");
address alice = makeAddr("alice");
address bob = makeAddr("bob");
// Configure engines
vm.prank(owner);
referral.configureEngine(engine1, true);
vm.prank(owner);
referral.configureEngine(engine2, true);
// Create same custom code in both engines
vm.prank(engine1);
referral.createCustomReferralCode(alice, "BESTCODE");
​
vm.prank(engine2);
referral.createCustomReferralCode(bob, "BESTCODE");
​
// Verify conflict
address result = referral.getCustomReferralCodeReferrer("BESTCODE");
assertEq(result, bob, "Engine2 overwrote Engine1's custom code");
vm.expectRevert();
assertEq(result, alice, "Should Revert because Engine2 overwrote Engine1's custom code");
​
//both engines see Bob's address now
address result2 = referral.getCustomReferralCodeReferrer("BESTCODE");
assertEq(result2, bob);
}
}

Impact

Severity Type Consequences
High Financial 🔸 Wrong people get referral rewards 🔸 Lost commissions for legitimate referrers 🔸 Users lose trust in referral system

Tools Used

  • Foundry (Blockchain testing framework)

  • Solidity (Smart contract language)

Recommendations

1. Add Engine Context to Storage

function load(address engine, string memory customCode) internal pure returns (Data storage) {
bytes32 slot = keccak256(abi.encode(
CUSTOM_REFERRAL_CONFIGURATION_LOCATION,
+ engine, // Include engine address
customCode
));
// ...
}

2.Track Code Origins

struct Data {
address referrer;
+ address createdBy; // Track which engine created it
}
​
function createCustomReferralCode(...) external {
// ...
+ config.createdBy = msg.sender; // msg.sender = engine
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 6 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Custom Referral Code Conflict Across Engines

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.