Summary
Because the updateReferrerInfo function only allows a self-referral to be created, the corresponding authority's real referrer cannot earn any referral bonus at all when the createTaker function is called.
Vulnerability Details
The following updateReferrerInfo function executes ReferralInfo storage referralInfo = referralInfoMap[_referrer] and referralInfo.referrer = _referrer. Since referralInfoMap[_referrer]'s referrer is such _referrer, calling the updateReferrerInfo function can only create a self-referral when msg.sender is not _referrer even though if (_msgSender() == _referrer) { revert InvalidReferrer(_referrer); } attempts to prevent a self-referral from being created. In other words, the updateReferrerInfo function cannot be used by the authority to set his referral to be someone who is not such authority.
https://github.com/Cyfrin/2024-08-tadle/blob/c249cdb68c37c47025cdc4c4782c8ee3f20a5b98/src/core/SystemConfig.sol#L41-L80
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 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;
...
}
When the createTaker function below is called, the referralInfo returned by systemConfig.getReferralInfo(_msgSender()) is the referralInfoMap[_referrer], where the _referrer is such msg.sender who is the authority. Yet, such referralInfoMap[_referrer]'s referrer would be the authority himself if the updateReferrerInfo function was called or such referralInfoMap[_referrer] would be empty if the updateReferrerInfo function was not called at all. Either way, none of the platformFee would be attributed to the authority's real referrer as such referrer's referral bonus when the _updateReferralBonus function below is called.
https://github.com/Cyfrin/2024-08-tadle/blob/c249cdb68c37c47025cdc4c4782c8ee3f20a5b98/src/core/SystemConfig.sol#L229-L233
function getReferralInfo(
address _referrer
) external view returns (ReferralInfo memory) {
return referralInfoMap[_referrer];
}
https://github.com/Cyfrin/2024-08-tadle/blob/c249cdb68c37c47025cdc4c4782c8ee3f20a5b98/src/core/PreMarkets.sol#L164-L284
function createTaker(address _offer, uint256 _points) external payable {
...
if (_points == 0x0) {
revert Errors.AmountIsZero();
}
OfferInfo storage offerInfo = offerInfoMap[_offer];
MakerInfo storage makerInfo = makerInfoMap[offerInfo.maker];
if (offerInfo.offerStatus != OfferStatus.Virgin) {
revert InvalidOfferStatus();
}
if (offerInfo.points < _points + offerInfo.usedPoints) {
revert NotEnoughPoints(
offerInfo.points,
offerInfo.usedPoints,
_points
);
}
...
ISystemConfig systemConfig = tadleFactory.getSystemConfig();
{
MarketPlaceInfo memory marketPlaceInfo = systemConfig
.getMarketPlaceInfo(makerInfo.marketPlace);
marketPlaceInfo.checkMarketPlaceStatus(
block.timestamp,
MarketPlaceStatus.Online
);
}
@> ReferralInfo memory referralInfo = systemConfig.getReferralInfo(
@> _msgSender()
);
uint256 platformFeeRate = systemConfig.getPlatformFeeRate(_msgSender());
...
address stockAddr = GenerateAddress.generateStockAddress(offerId);
if (stockInfoMap[stockAddr].authority != address(0x0)) {
revert StockAlreadyExist();
}
...
uint256 depositAmount = _points.mulDiv(
offerInfo.amount,
offerInfo.points,
Math.Rounding.Ceil
);
uint256 platformFee = depositAmount.mulDiv(
platformFeeRate,
Constants.PLATFORM_FEE_DECIMAL_SCALER
);
uint256 tradeTax = depositAmount.mulDiv(
makerInfo.eachTradeTax,
Constants.EACH_TRADE_TAX_DECIMAL_SCALER
);
ITokenManager tokenManager = tadleFactory.getTokenManager();
_depositTokenWhenCreateTaker(
platformFee,
depositAmount,
tradeTax,
makerInfo,
offerInfo,
tokenManager
);
offerInfo.usedPoints = offerInfo.usedPoints + _points;
...
stockInfoMap[stockAddr] = StockInfo({
id: offerId,
stockStatus: StockStatus.Initialized,
stockType: offerInfo.offerType == OfferType.Ask
? StockType.Bid
: StockType.Ask,
authority: _msgSender(),
maker: offerInfo.maker,
preOffer: _offer,
points: _points,
amount: depositAmount,
offer: address(0x0)
});
offerId = offerId + 1;
@> uint256 remainingPlatformFee = _updateReferralBonus(
platformFee,
depositAmount,
stockAddr,
makerInfo,
@> referralInfo,
tokenManager
);
makerInfo.platformFee = makerInfo.platformFee + remainingPlatformFee;
_updateTokenBalanceWhenCreateTaker(
_offer,
tradeTax,
depositAmount,
offerInfo,
makerInfo,
tokenManager
);
...
}
https://github.com/Cyfrin/2024-08-tadle/blob/c249cdb68c37c47025cdc4c4782c8ee3f20a5b98/src/core/PreMarkets.sol#L839-L904
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 {
...
uint256 referrerReferralBonus = platformFee.mulDiv(
referralInfo.referrerRate,
Constants.REFERRAL_RATE_DECIMAL_SCALER,
Math.Rounding.Floor
);
...
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;
...
}
}
Impact
When the createTaker function is called, the corresponding authority's real referrer cannot earn any referral bonus at all.
Tools Used
Manual Review
Recommended Mitigation
https://github.com/Cyfrin/2024-08-tadle/blob/c249cdb68c37c47025cdc4c4782c8ee3f20a5b98/src/core/SystemConfig.sol#L69 can be updated to the following code.
ReferralInfo storage referralInfo = referralInfoMap[_msgSender()];