Platform: Code4rena
Start Date: 09/12/2022
Pot Size: $36,500 USDC
Total HM: 9
Participants: 69
Period: 3 days
Judge: Picodes
Total Solo HM: 2
Id: 190
League: ETH
Rank: 17/69
Findings: 4
Award: $454.91
🌟 Selected for report: 0
🚀 Solo Findings: 0
210.7761 USDC - $210.78
Detailed description of the impact of this finding.
The if-else-statement only checks the userWithdrawLimitPerPeriod
constraint for the else part, not for the if part. As a result, a user can withdraw any amount in a new period and the contract will fail to detect such violation.
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
https://github.com/prepo-io/prepo-monorepo/blob/3541bc704ab185a969f300e96e2f744a572a3640/apps/smart-contracts/core/contracts/WithdrawHook.sol#L66-L72
The userWithdrawLimitPerPeriod
constraint needs to be checked for the first withdraw as well as for later withdraw in a period. Failing to check for the first withdraw in a period will result a violation of the contraint and as a result, a user can withdraw as much as he wants.
Remix
A correction is as follows which includes a check in both cases (if case and else case):
if (lastUserPeriodReset + userPeriodLength < block.timestamp) { lastUserPeriodReset = block.timestamp; require(_amountBeforeFee <= userWithdrawLimitPerPeriod, "user withdraw limit exceeded"); userToAmountWithdrawnThisPeriod[_sender] = _amountBeforeFee; } else { require(userToAmountWithdrawnThisPeriod[_sender] + _amountBeforeFee <= userWithdrawLimitPerPeriod, "user withdraw limit exceeded"); userToAmountWithdrawnThisPeriod[_sender] += _amountBeforeFee; }
#0 - hansfriese
2022-12-13T15:29:08Z
duplicate of #310
#1 - c4-judge
2022-12-13T19:06:44Z
Picodes marked the issue as duplicate of #310
#2 - c4-judge
2023-01-01T17:19:41Z
Picodes marked the issue as satisfactory
🌟 Selected for report: trustindistrust
190.9616 USDC - $190.96
Detailed description of the impact of this finding.
The send()
function will fail in three cases: 1) scaledPrice <= _scaledPriceLowerBound; 2) outputAmount == 0; and 3) outputAmount > _outputToken.balanceOf(address(this)). Unfortunately, the function just return so that the caller will not detect the failure and assume everything is fine. For example, the caller will assume the fee has been sent to the recipient successfully even though no fee has been sent
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
https://github.com/prepo-io/prepo-monorepo/blob/3541bc704ab185a969f300e96e2f744a572a3640/apps/smart-contracts/core/contracts/TokenSender.sol#L36
The send()
function should revert under the three failing conditions so that the caller know the fee has not been sent successful. Or, it can return a boolean value so that the caller can decide what to do when fee has not been sent successfully.
Remix
Either revert on the failing condition or return false under such condition.
#0 - c4-judge
2022-12-14T07:40:59Z
Picodes marked the issue as duplicate of #311
#1 - c4-judge
2023-01-07T11:41:35Z
Picodes marked the issue as duplicate of #257
#2 - Picodes
2023-01-07T11:51:30Z
The impact does not explain why in this context this is an issue. As the rebate is optional the described behavior could make sense, so the explanation needs to be more detailed.
#3 - c4-judge
2023-01-07T15:15:20Z
Picodes marked the issue as partial-50
#4 - c4-judge
2023-01-09T20:34:05Z
Picodes changed the severity to 2 (Med Risk)
🌟 Selected for report: 0xSmartContract
Also found by: 0Kage, 0x52, 0xAgro, 0xNazgul, 0xTraub, 0xhacksmithh, Awesome, Aymen0909, Bnke0x0, Englave, Janio, Mukund, Parth, RaymondFam, Rolezn, SmartSek, Tointer, UdarTeam, Udsen, Zarf, caventa, chaduke, csanuragjain, deliriusz, gz627, idkwhatimdoing, izhelyazkov, joestakey, neumo, obront, oyc_109, rvierdiiev, shark, trustindistrust, wait, yongskiws
28.124 USDC - $28.12
QA1: https://github.com/prepo-io/prepo-monorepo/blob/3541bc704ab185a969f300e96e2f744a572a3640/apps/smart-contracts/core/contracts/Collateral.sol#L65 The withdraw function assumes that the collatoral token has 18 decimals, as a result, the withdraw function will not work when the collatoral has different number of decimals.
QA2: https://github.com/prepo-io/prepo-monorepo/blob/3541bc704ab185a969f300e96e2f744a572a3640/apps/smart-contracts/core/contracts/Collateral.sol#L45
A common mistake is that user might specify the collatoral
's contract address as the recipient address, so it would be helpful to check to make sure that is not the case
require(_recipient != address(this) && _recipient != address(0x0), "wrong recipient address")
QA3: https://github.com/prepo-io/prepo-monorepo/blob/3541bc704ab185a969f300e96e2f744a572a3640/apps/smart-contracts/core/contracts/Collateral.sol#L49
The function assumes that an approval from msg.sender
has been made before the function call. This assumption needs to be made explicit as a documentation for the function; otherwise, the function will revert .
QA4: https://github.com/prepo-io/prepo-monorepo/blob/3541bc704ab185a969f300e96e2f744a572a3640/apps/smart-contracts/core/contracts/TokenSender.sol#L50
A range check is necessary for __priceMultiplier
, for example, __priceMultiplier <= MULTIPLIER_DENOMINATOR
.
QA5: https://github.com/prepo-io/prepo-monorepo/blob/3541bc704ab185a969f300e96e2f744a572a3640/apps/smart-contracts/core/contracts/Collateral.sol#L85 Zero address check is needed so that fee will not be lost to the zero address of manager.
QA6: lock all contracts at the most recent version of solidity, 0.8.17.
QA7: https://github.com/prepo-io/prepo-monorepo/blob/3541bc704ab185a969f300e96e2f744a572a3640/apps/smart-contracts/core/contracts/PrePOMarket.sol#L65
It is assumed that an allowance has been approved by msg.sender
to PrePOMarket
before the function call. Such assumption needs to be made explicit in documentation - otherwise, the function will revert.
QA8: https://github.com/prepo-io/prepo-monorepo/blob/3541bc704ab185a969f300e96e2f744a572a3640/apps/smart-contracts/core/contracts/PrePOMarket.sol#L70-L71 It is assumed that both the collatoral token and the LongShortTokens have 18 decimals. This assumption needs to be explicit.
QA9: https://github.com/prepo-io/prepo-monorepo/blob/3541bc704ab185a969f300e96e2f744a572a3640/apps/smart-contracts/core/contracts/PrePOMarket.sol#L65
Needs to check zero amount for _amount
.
QA10. https://github.com/prepo-io/prepo-monorepo/blob/3541bc704ab185a969f300e96e2f744a572a3640/apps/smart-contracts/core/contracts/PrePOMarket.sol#L42
Needs to check the contraint: _floorValuation < _ceilingValuation
.
GA11 https://github.com/prepo-io/prepo-monorepo/blob/3541bc704ab185a969f300e96e2f744a572a3640/apps/smart-contracts/core/contracts/PrePOMarket.sol#L65-L76
Lacks the check of expiry time: require( block.timestamp < _expiryTime, "market expires");
GA12:https://github.com/prepo-io/prepo-monorepo/blob/3541bc704ab185a969f300e96e2f744a572a3640/apps/smart-contracts/core/contracts/TokenSender.sol#L42 Instead of using transfer(), which is not safe, use the safeTransfer instead: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/utils/SafeERC20.sol
GA13: https://github.com/prepo-io/prepo-monorepo/blob/3541bc704ab185a969f300e96e2f744a572a3640/packages/prepo-shared-contracts/contracts/TokenSenderCaller.sol#L16
Zero address check is needed for the _treasury
, otherwise, fee might be sent to the zero address and get lost forever.
#0 - c4-judge
2022-12-19T14:35:11Z
Picodes marked the issue as grade-b
#1 - c4-judge
2022-12-19T14:36:22Z
Picodes marked the issue as grade-a
#2 - ramenforbreakfast
2022-12-21T22:27:28Z
I think this report should be put in a lower tier grading, findings listed are either of dubious value or low quality.
QA1 and QA8 are just low value observations about if our own tokens were not 18 decimals?
QA2 Doesn't make any sense, why would recipient
be confused for the Collateral
address?
QA5 Doesn't make any sense because no fee is sent to manager
, manager
can only withdraw assets which obviously can't be done by zero address
QA9 Why do we have to check for 0?
GA11 Is an issue declared out-of-scope
#3 - c4-sponsor
2022-12-21T23:23:37Z
ramenforbreakfast marked the issue as sponsor disputed
#4 - Picodes
2023-01-07T18:10:28Z
Giving grade b for this report despite the number of issues following the sponsor's comment
#5 - c4-judge
2023-01-07T18:10:32Z
Picodes marked the issue as grade-b
🌟 Selected for report: ReyAdmirado
Also found by: 0xSmartContract, 0xTraub, Aymen0909, Englave, Mukund, RHaO-sec, RaymondFam, Rolezn, Sathish9098, Tomio, UdarTeam, chaduke, dharma09, gz627, martin, nadin, pavankv, rjs, saneryee
25.0472 USDC - $25.05
G1. https://github.com/prepo-io/prepo-monorepo/blob/3541bc704ab185a969f300e96e2f744a572a3640/packages/prepo-shared-contracts/contracts/NFTScoreRequirement.sol#L13 Dropping the first condition can save gas since the second condition already implies the first. Although this is a read function, it might be called by a write function.
return getAccountScore(account) >= _requiredScore;
G2. https://github.com/prepo-io/prepo-monorepo/blob/3541bc704ab185a969f300e96e2f744a572a3640/packages/prepo-shared-contracts/contracts/NFTScoreRequirement.sol#L22 Changing the arguments to calldata will save gas.
function setCollectionScores(IERC721[] calldata collections, uint256[] calldata scores) public virtual override
G3. https://github.com/prepo-io/prepo-monorepo/blob/3541bc704ab185a969f300e96e2f744a572a3640/packages/prepo-shared-contracts/contracts/NFTScoreRequirement.sol#L35 Changing the arguments to calldata will save gas.
function removeCollections(IERC721[] calldata collections) public virtual override
G4. https://github.com/prepo-io/prepo-monorepo/blob/3541bc704ab185a969f300e96e2f744a572a3640/apps/smart-contracts/core/contracts/WithdrawHook.sol#L64 Change it to the following line to save gas:
globalAmountWithdrawnThisPeriod = globalAmountWithdrawnThisPeriod+_amountBeforeFee
G5. https://github.com/prepo-io/prepo-monorepo/blob/3541bc704ab185a969f300e96e2f744a572a3640/apps/smart-contracts/core/contracts/PrePOMarket.sol#L95
After line 94, _collateralAllowanceBefore
is actually equal to _expectedFee
, so instead of introducing a new variable _collateralAllowanceBefore
and calculating its values via collateral.allowance()
, we can save gas by simply using _expectedFee
.
G6: https://github.com/prepo-io/prepo-monorepo/blob/3541bc704ab185a969f300e96e2f744a572a3640/apps/smart-contracts/core/contracts/PrePOMarketFactory.sol#L22 Changing memory variables to calldata vairables can save gas.
function createMarket(string calldata _tokenNameSuffix, string calldata _tokenSymbolSuffix, bytes32 longTokenSalt, bytes32 shortTokenSalt, address _governance, address _collateral, uint256 _floorLongPrice, uint256 _ceilingLongPrice, uint256 _floorValuation, uint256 _ceilingValuation, uint256 _expiryTime) external override onlyOwner nonReentrant {
G7 https://github.com/prepo-io/prepo-monorepo/blob/3541bc704ab185a969f300e96e2f744a572a3640/apps/smart-contracts/core/contracts/PrePOMarketFactory.sol#L41 Changing memory variables to calldata vairables can save gas.
function _createPairTokens(string calldata _tokenNameSuffix, string calldata _tokenSymbolSuffix, bytes32 _longTokenSalt, bytes32 _shortTokenSalt) internal returns (LongShortToken _newLongToken, LongShortToken _newShortToken) {
G8 https://github.com/prepo-io/prepo-monorepo/blob/3541bc704ab185a969f300e96e2f744a572a3640/apps/smart-contracts/core/contracts/Collateral.sol#L53 Checking whether _fee is equal to zero will save gas:
if(_Fee > 0) withdrawHook.hook(msg.sender, _baseTokenAmount, _baseTokenAmountAfterFee);
In this way, we do not have to check whether _fee > 0
again inside the hookd()
function.
G9: https://github.com/prepo-io/prepo-monorepo/blob/3541bc704ab185a969f300e96e2f744a572a3640/apps/smart-contracts/core/contracts/Collateral.sol#L80
might as well check whether _amount == 0
to save gas.
#0 - Picodes
2022-12-19T13:27:25Z
G1: false: the first condition avoids making the call
#1 - c4-judge
2022-12-19T13:27:30Z
Picodes marked the issue as grade-b