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)
{
GlobalConfiguration.Data storage globalConfiguration = GlobalConfiguration.load();
tradingAccountId = ++globalConfiguration.nextAccountId;
IAccountNFT tradingAccountToken = IAccountNFT(globalConfiguration.tradingAccountToken);
TradingAccount.create(tradingAccountId, msg.sender);
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();
}
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;
}
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 });
uint128 tradingAccountId = perpsEngine.createTradingAccount(bytes(""), false);
assertEq(tradingAccountId, 1, "createTradingAccount");
changePrank({ msgSender: users.owner.account });
perpsEngine.createCustomReferralCode(users.naruto.account, customReferralCode);
changePrank({ msgSender: users.naruto.account });
bytes memory referralCode = bytes(customReferralCode);
vm.expectEmit({ emitter: address(perpsEngine) });
emit TradingAccountBranch.LogReferralSet(users.naruto.account, users.naruto.account, referralCode, true);
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;
}