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

M-02. A user who has a `customRefferalCode` pointing to his address can set himself as a referral.

https://github.com/Cyfrin/2024-07-zaros/blob/d687fe96bb7ace8652778797052a38763fbcbb1b/src/perpetuals/branches/TradingAccountBranch.sol#L229-L280

Summary

If a user possesses a customReferralCode that directs to their address, he may call the TradingAccountBranch::createTradingAccount function with the following parameters:

  • referralCode: This parameter should be set to the user's customReferralCode, which points to their address

  • isCustomReferralCode: This flag should be set to true.

Thus, the user will be rewarded for referring himself to the protocol.

Vulnerability Details

Owner can create customReferralCode for users by calling GlobalConfigurationBranch::createCustomReferralCode. Link to code

Every user who has this kind of code may call the TradingAccountBranch::createTradingAccount function with this code as a param.

Link to code

FILE: src/perpetuals/branches/TradingAccountBranch.sol::createTradingAccount
254: Referral.Data storage referral = Referral.load(msg.sender);
if (referralCode.length != 0 && referral.referralCode.length == 0) {
if (isCustomReferralCode) {
CustomReferralConfiguration.Data storage customReferral =
CustomReferralConfiguration.load(string(referralCode));
if (customReferral.referrer == address(0)) {
revert Errors.InvalidReferralCode();
}
referral.referralCode = referralCode;
referral.isCustomReferralCode = true;
} else {
address referrer = abi.decode(referralCode, (address));
if (referrer == msg.sender) {
revert Errors.InvalidReferralCode();
}
referral.referralCode = referralCode;
referral.isCustomReferralCode = false;
}
emit LogReferralSet(msg.sender, referral.getReferrerAddress(), referralCode, isCustomReferralCode);
277: }

If the referral code has not yet been set for this user and isCustomReferralCode == true Link to code, the function does not verify whether the message sender's address matches the referral code. If referrer != address(0) function set referralCode, passed as a parameter, as a referral code for this user.

Proof of Concept

Add new test to the file createTradingAccount.t.sol

function test_DontRevertWhen_TheReferralCodeIsEqualToMsgSender()
external
givenTheTradingAccountTokenIsSet
whenTheUserHasAReferralCode
whenTheReferralCodeIsNotCustom
{
string memory customReferralCode = "customReferralCode";
// owner creates customReferralCode for user
changePrank({ msgSender: users.owner.account });
perpsEngine.createCustomReferralCode(users.naruto.account, customReferralCode);
changePrank({ msgSender: users.naruto.account });
// it should emit {LogReferralSet} event
vm.expectEmit({ emitter: address(perpsEngine) });
emit TradingAccountBranch.LogReferralSet(users.naruto.account, users.naruto.account, bytes(customReferralCode), true);
perpsEngine.createTradingAccount(bytes(customReferralCode), true);
}

Run the test: forge test --mt test_DontRevertWhen_TheReferralCodeIsEqualToMsgSender

2024-07-zaros$ forge test --mt test_DontRevertWhen_TheReferralCodeIsEqualToMsgSender
[⠒] Compiling...
[⠒] Compiling 1 files with Solc 0.8.25
[⠢] Solc 0.8.25 finished in 5.93s
Compiler run successful!
Ran 1 test for test/integration/perpetuals/trading-account-branch/createTradingAccount/createTradingAccount.t.sol:CreateTradingAccount_Integration_Test
[PASS] test_DontRevertWhen_TheReferralCodeIsEqualToMsgSender() (gas: 314415)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 9.26ms (178.19µs CPU time)
Ran 1 test suite in 10.53ms (9.26ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)

We can see the test passing.

Impact

User with customReferralCode pointing to himself receives benefits not provided by the protocol, which contradicts its logic. The protocol aims to attract new users by rewarding referrals. However, in this scenario, although rewards are given, they do not result in new users.

Tools Used

Manual Review

Recommendations

Add the require in the TradingAccountBranch::createTradingAccount function to check that referralCode doesn't point to user's address.

function createTradingAccount(
bytes memory referralCode,
bool isCustomReferralCode
)
public
virtual
returns (uint128 tradingAccountId)
{
// fetch storage slot for global config
GlobalConfiguration.Data storage globalConfiguration = GlobalConfiguration.load();
// increment next account id & output
tradingAccountId = ++globalConfiguration.nextAccountId;
// get refrence to account nft token
IAccountNFT tradingAccountToken = IAccountNFT(globalConfiguration.tradingAccountToken);
// create account record
TradingAccount.create(tradingAccountId, msg.sender);
// mint nft token to account owner
tradingAccountToken.mint(msg.sender, tradingAccountId);
emit LogCreateTradingAccount(tradingAccountId, msg.sender);
Referral.Data storage referral = Referral.load(msg.sender);
if (referralCode.length != 0 && referral.referralCode.length == 0) {
if (isCustomReferralCode) {
CustomReferralConfiguration.Data storage customReferral =
CustomReferralConfiguration.load(string(referralCode));
if (customReferral.referrer == address(0)) {
revert Errors.InvalidReferralCode();
}
+ if (customReferral.referrer == msg.sender) {
+ revert Errors.InvalidReferralCode();
+ }
referral.referralCode = referralCode;
referral.isCustomReferralCode = true;
} else {
address referrer = abi.decode(referralCode, (address));
if (referrer == msg.sender) {
revert Errors.InvalidReferralCode();
}
referral.referralCode = referralCode;
referral.isCustomReferralCode = false;
}
emit LogReferralSet(msg.sender, referral.getReferrerAddress(), referralCode, isCustomReferralCode);
}
return tradingAccountId;
}
Updates

Lead Judging Commences

inallhonesty Lead Judge
11 months ago
inallhonesty Lead Judge 11 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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