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";
changePrank({ msgSender: users.owner.account });
perpsEngine.createCustomReferralCode(users.naruto.account, customReferralCode);
changePrank({ msgSender: users.naruto.account });
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;
}