DeFiFoundry
60,000 USDC
View results
Submission Details
Severity: low
Valid

TradingAccountBranch::createTradingAccount allows self-referral when using a custom referral code

Summary

Users can set a referrer when creating a new trading account. The referrer address will then receive a reward. However, users can be their own referrer by creating a new account using their own previously created custom referral code.

Vulnerability Details

TradingAccountBranch::createTradingAccount function takes a referral code as a parameter in order to distribute rewards to the code owner. When using a non-custom referral code, there is a security check to prevent the users from referring themselves. However, if a custom referral code is used, such verification does not exist.

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)) { //@audit Only checked against zero address
revert Errors.InvalidReferralCode();
}
referral.referralCode = referralCode;
referral.isCustomReferralCode = true;
} else {
address referrer = abi.decode(referralCode, (address));
@> if (referrer == msg.sender) { //@audit The user cannot self-refer when using a non-custom referral code
revert Errors.InvalidReferralCode();
}
referral.referralCode = referralCode;
referral.isCustomReferralCode = false;
}
emit LogReferralSet(msg.sender, referral.getReferrerAddress(), referralCode, isCustomReferralCode);
}
return tradingAccountId;
}

Proof of concept

Add the following test to the test/integration/perpetuals/trading-account-branch/createTradingAccount/createTradingAccount.t.sol file.

function test_WhenTheReferralCodeIsEqualToMsgSenderCustomReferralCode()
external
givenTheTradingAccountTokenIsSet
whenTheUserHasAReferralCode
whenTheReferralCodeIsCustom
{
string memory customReferralCode = "customReferralCode";
changePrank({ msgSender: users.naruto.account });
// User creates a first trading account, without referral code
uint128 tradingAccountId = perpsEngine.createTradingAccount(bytes(""), false);
assertEq(tradingAccountId, 1, "createTradingAccount");
// A custom referral code for the user is created
changePrank({ msgSender: users.owner.account });
perpsEngine.createCustomReferralCode(users.naruto.account, customReferralCode);
changePrank({ msgSender: users.naruto.account });
bytes memory referralCode = bytes(customReferralCode);
// it should emit {LogReferralSet} event
vm.expectEmit({ emitter: address(perpsEngine) });
emit TradingAccountBranch.LogReferralSet(users.naruto.account, users.naruto.account, referralCode, true);
// User successfully creates a second trading account using his own custom referral code
tradingAccountId = perpsEngine.createTradingAccount(referralCode, true);
assertEq(tradingAccountId, 2, "createTradingAccount");
}

Impact

Impact: Medium

Likelihood: High

Any user can take advantage of this issue to self-referring by creating a new trading account using their own custom referral code, earning the corresponding referrer rewards.

Tools Used

Manual Review

Recommendations

It is recommended to add additional verification when using a custom referral code to prevent self-referral.

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)) {
+ if (customReferral.referrer == address(0) || 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
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Appeal created

crunter Submitter
about 1 year ago
inallhonesty Lead Judge
about 1 year ago
crunter Submitter
about 1 year ago
inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Referrals should be set per trading account id instead of per trader

Support

FAQs

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