Summary
Implementations of ReferralInfo may not work as expected.
Vulnerability Details
Administrators update referral extra rates via SystemConfig::updateReferralExtraRateMap().
function updateReferralExtraRateMap(
address _referrer,
uint256 _extraRate
) external onlyOwner {
uint256 totalRate = _extraRate + baseReferralRate;
if (totalRate > Constants.REFERRAL_RATE_DECIMAL_SCALER) {
revert InvalidTotalRate(totalRate);
}
@> referralExtraRateMap[_referrer] = _extraRate;
emit UpdateReferralExtraRateMap(_referrer, _extraRate);
}
when referralExtraRateMap[_referrer] > 0, anyone other than _referrer can call SystemConfig::updateReferrerInfo() to update the referrer setting. Note that referralInfo.referrer = _referrer.
function updateReferrerInfo(
address _referrer,
uint256 _referrerRate,
uint256 _authorityRate
) external {
@> uint256 referralExtraRate = referralExtraRateMap[_referrer];
uint256 totalRate = baseReferralRate + referralExtraRate;
if (totalRate > Constants.REFERRAL_RATE_DECIMAL_SCALER) {
revert InvalidTotalRate(totalRate);
}
if (_referrerRate + _authorityRate != totalRate) {
revert InvalidRate(_referrerRate, _authorityRate, totalRate);
}
@> ReferralInfo storage referralInfo = referralInfoMap[_referrer];
@> referralInfo.referrer = _referrer;
referralInfo.referrerRate = _referrerRate;
referralInfo.authorityRate = _authorityRate;
emit UpdateReferrerInfo(
msg.sender,
_referrer,
_referrerRate,
_authorityRate
);
}
When a user calls PreMarkets::createTaker() to create a Taker, ReferralInfo is obtained through systemConfig.getReferralInfo(_msgSender()). Obviously, referralInfo.referrer == _msgSender() is used here. Then PreMarkets::_updateReferralBonus() is called to update the referral bonus.
function createTaker(address _offer, uint256 _points) external payable {
@> ReferralInfo memory referralInfo = systemConfig.getReferralInfo(
_msgSender()
);
offerId = offerId + 1;
@> uint256 remainingPlatformFee = _updateReferralBonus(
platformFee,
depositAmount,
stockAddr,
makerInfo,
@> referralInfo,
tokenManager
);
}
function getReferralInfo(
address _referrer
) external view returns (ReferralInfo memory) {
@> return referralInfoMap[_referrer];
}
However, we noticed that TokenManager::addTokenBalance() was called twice in the PreMarkets::_updateReferralBonus() function to update the balance. If referralInfo.referrer == _msgSender() is expected, the balance should not be updated in this repeated manner. Combined with the relevant content of event ReferralBonus(), it is inferred that the relevant code implementation may not be consistent with expectations.
function _updateReferralBonus(
uint256 platformFee,
uint256 depositAmount,
address stockAddr,
MakerInfo storage makerInfo,
ReferralInfo memory referralInfo,
ITokenManager tokenManager
) internal returns (uint256 remainingPlatformFee) {
if (referralInfo.referrer == address(0x0)) {
remainingPlatformFee = platformFee;
} else {
tokenManager.addTokenBalance(
TokenBalanceType.ReferralBonus,
@> referralInfo.referrer,
makerInfo.tokenAddress,
@> referrerReferralBonus
);
uint256 authorityReferralBonus = platformFee.mulDiv(
referralInfo.authorityRate,
Constants.REFERRAL_RATE_DECIMAL_SCALER,
Math.Rounding.Floor
);
tokenManager.addTokenBalance(
TokenBalanceType.ReferralBonus,
@> _msgSender(),
makerInfo.tokenAddress,
@> authorityReferralBonus
);
remainingPlatformFee =
platformFee -
referrerReferralBonus -
authorityReferralBonus;
emit ReferralBonus(
stockAddr,
@> _msgSender(),
@> referralInfo.referrer,
authorityReferralBonus,
referrerReferralBonus,
depositAmount,
platformFee
);
}
event ReferralBonus(
address indexed stock,
@> address authority,
@> address referrer,
uint256 authorityReferralBonus,
uint256 referrerReferralBonus,
uint256 tradingVolume,
uint256 tradingFee
);
Code Snippet
https://github.com/Cyfrin/2024-08-tadle/blob/04fd8634701697184a3f3a5558b41c109866e5f8/src/core/SystemConfig.sol#L199-L209
https://github.com/Cyfrin/2024-08-tadle/blob/04fd8634701697184a3f3a5558b41c109866e5f8/src/core/SystemConfig.sol#L41-L80
https://github.com/Cyfrin/2024-08-tadle/blob/04fd8634701697184a3f3a5558b41c109866e5f8/src/core/PreMarkets.sol#L164-L284
https://github.com/Cyfrin/2024-08-tadle/blob/04fd8634701697184a3f3a5558b41c109866e5f8/src/core/SystemConfig.sol#L228-L233
Impact
Implementations of ReferralInfo may not work as expected.
Tools Used
Manual Review
Recommendations
Check and modify ReferralInfo related code.
I guess the problem may exist in the SystemConfig::updateReferrerInfo() function:
Consider the following method modification, which can ensure the permission restrictions of function calls and make the code work as expected.
function updateReferrerInfo(
address _referrer,
uint256 _referrerRate,
uint256 _authorityRate
) external {
if (_msgSender() == _referrer) {
revert InvalidReferrer(_referrer);
}
if (_referrer == address(0x0)) {
revert Errors.ZeroAddress();
}
if (_referrerRate < baseReferralRate) {
revert InvalidReferrerRate(_referrerRate);
}
- uint256 referralExtraRate = referralExtraRateMap[_referrer];
+ uint256 referralExtraRate = referralExtraRateMap[_msgSender()];
uint256 totalRate = baseReferralRate + referralExtraRate;
if (totalRate > Constants.REFERRAL_RATE_DECIMAL_SCALER) {
revert InvalidTotalRate(totalRate);
}
if (_referrerRate + _authorityRate != totalRate) {
revert InvalidRate(_referrerRate, _authorityRate, totalRate);
}
- ReferralInfo storage referralInfo = referralInfoMap[_referrer];
+ ReferralInfo storage referralInfo = referralInfoMap[_msgSender()];
referralInfo.referrer = _referrer;
referralInfo.referrerRate = _referrerRate;
referralInfo.authorityRate = _authorityRate;
emit UpdateReferrerInfo(
msg.sender,
_referrer,
_referrerRate,
_authorityRate
);
}