Platform: Code4rena
Start Date: 10/06/2021
Pot Size: $45,000 USDC
Total HM: 21
Participants: 12
Period: 7 days
Judge: LSDan
Total Solo HM: 13
Id: 13
League: ETH
Rank: 5/12
Findings: 5
Award: $3,103.21
🌟 Selected for report: 2
🚀 Solo Findings: 1
shw
In the contract RCTreasury
, the return values of ERC20 transfer
and transferFrom
are not checked, which could be false
if the transferred token is not ERC20-compliant. In that case, the transfer fails without being noticed by the calling contract.
Referenced code: RCTreasury.sol#L350 RCTreasury.sol#L298 RCTreasury.sol#L373 RCTreasury.sol#L478
Use the SafeERC20
library implementation from Openzeppelin and call safeTransfer
or safeTransferFrom
when transferring ERC20 tokens.
#0 - Splidge
2021-06-17T10:47:29Z
Duplicate of #2
#1 - dmvt
2021-07-11T12:38:14Z
duplicate of #2
shw
The function sponsor(address _sponsorAddress, uint256 _amount)
in RCMarket
is permissionless, which allows a malicious actor to sponsor any market in the name of some sponsor as long as enough tokens are approved. The malicious actor could then add sponsorship to markets where he will likely become the winner to gain more winning awards.
sponsor(100)
on MarketA.sponsor(Alice, 100)
on MarketB.Besides front-running, Bob can also check the past transactions to find anyone who approved the treasury before. If the current allowance is greater than zero, he can call sponsor
directly with the victim's address as the parameter to sponsor any market he wants.
Referenced code: RCMarket.sol#L817-L822
Should check if the caller of the function sponsor(address _sponsorAddress, uint256 _amount)
is the factory.
#0 - Splidge
2021-06-17T10:43:37Z
Duplicate of #40
#1 - dmvt
2021-07-11T12:39:54Z
duplicate of #40
shw
In the function createMarket
of RCFactory
, the market opening time is not checked before the market locking time, and thus markets can be created with invalid time configurations accidentally. Besides, if the advancedWarning
is non-zero, there are two checks applied, and the first seems unnecessary (which is a requirement of the second one).
Referenced code: RCFactory.sol#L520-L527
Check that the market opening time is before both the market locking time. In cases where advancedWarning
is non-zero, if the purpose of the first check is to prevent subtraction overflow on the second check, consider removing the first one and re-writing the second one as _timestamps[0] > block.timestamp + advancedWarning
.
#0 - Splidge
2021-06-17T10:54:47Z
Duplicate of #61
#1 - dmvt
2021-07-10T13:43:28Z
duplicate of #61
🌟 Selected for report: shw
1302.1578 USDC - $1,302.16
shw
The function foreclosureTimeUser
of RCTreasury
underestimates the user's foreclosure time if the current time is not the user's last rent calculation time. The underestimation of the foreclosure time could cause wrong results when determining the new owner of the card.
The variable timeLeftOfDeposit
at line 668 is calculated based on depositAbleToWithdraw(_user)
, the user's deposit minus the rent from the last rent calculation to the current time. Thus, the variable timeLeftOfDeposit
indicates the time left of deposit, starting from now. However, at line 672, the foreclosureTimeWithoutNewCard
is calculated by timeLeftOfDeposit
plus the user's last rent calculation time instead of the current time. As a result, the user's foreclosure time is reduced. From another perspective, the rent between the last rent calculation time and the current time is counted twice.
Referenced code: RCTreasury.sol#L642-L653 RCTreasury.sol#L669 RCTreasury.sol#L672 RCTreasury.sol#L678 RCOrderbook.sol#L553-L557
Change depositAbleToWithdraw(_user)
at line 669 to user[_user].deposit
. Or, change user[_user].lastRentCalc
at both line 672 and 678 to block.timestamp
.
#0 - Splidge
2021-06-22T09:58:07Z
phew, this was one to wrap your head around.
I went with the first recommended mitigation because I believe the second one could causes issues if the user had already foreclosed, depositAbleToWithdraw
would return 0 and so foreclosureTimeWithoutNewCard
would incorrectly show as block.timestamp
. Fix implemented here
Really nice spot this one. Many thanks for such an in-depth look into the maths.
shw
In the function deposit
of RCTreasury
, a user's foreclosure can be canceled even if he did not deposit enough tokens for the minimum rent.
During a deposit, at line 303, the user's deposit is increased by the deposit amount. However, at line 309, when checking whether to cancel the user's foreclosure, user[_user].deposit + _amount
is compared to the minimum rent. That is, the deposit amount is counted twice. Suppose that the minimum rent is x
, then the foreclosed user only needs to deposit x/2 + 1
to cancel his foreclosure.
Referenced code: RCTreasury.sol#L303 RCTreasury.sol#L309
Change user[_user].deposit + _amount
to user[_user].deposit
at line 309.
#0 - Splidge
2021-06-17T10:42:23Z
Duplicate of #26
#1 - dmvt
2021-07-09T15:22:09Z
I generally think the impact of this on the rest of the system is minimal. It results in a slight advantage to the user in foreclosure, but does not cause a loss or granting of additional funds. To take advantage of this exploit, the user would also need to be highly skilled at reading source code to find the exploit in the first place. Even if they took the time to do this, the effect would not be permanent. I'm aligned with the view expressed in #26 and #37 that this is low severity.
#2 - dmvt
2021-07-11T10:47:04Z
duplicate of #108
🌟 Selected for report: shw
434.0526 USDC - $434.05
shw
The variable domainSeperator
in EIP712Base
is cached in the contract storage and will not change after the contract is initialized. However, if a hard fork happens after the contract deployment, the domainSeperator
would become invalid on one of the forked chains due to the block.chainid
has changed.
Referenced code: EIP712Base.sol#L25-L44
Consider using the implementation from OpenZeppelin, which recalculates the domain separator if the current block.chainid
is not the cached chain ID.
#0 - Splidge
2021-06-18T15:11:49Z
The OpenZeppelin implementation can't be used because of the contracts uses a proxy pattern and so can't use the constructor in the OpenZeppelin version.
Instead I have taken the same method OpenZeppelin use to get a new domainSeperator
, here
#1 - Splidge
2021-06-21T15:58:34Z
I've noticed that there is an upgradable version of the OpenZeppelin metaTx contracts. I'll try and work on using them instead of the fix used above.