Platform: Code4rena
Start Date: 22/09/2022
Pot Size: $30,000 USDC
Total HM: 12
Participants: 133
Period: 3 days
Judge: 0xean
Total Solo HM: 2
Id: 165
League: ETH
Rank: 45/133
Findings: 3
Award: $60.48
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: ladboy233
Also found by: 0xSmartContract, 8olidity, Aymen0909, Chom, IllIllI, OptimismSec, PaludoX0, TomJ, ayeslick, cccz, csanuragjain, joestakey, neko_nyaa, pashov, peritoflores, rbserver, rvierdiiev
19.982 USDC - $19.98
https://github.com/code-423n4/2022-09-frax/blob/55ea6b1ef3857a277e2f47d42029bc0f3d6f9173/src/frxETHMinter.sol#L191 https://github.com/code-423n4/2022-09-frax/blob/55ea6b1ef3857a277e2f47d42029bc0f3d6f9173/src/frxETHMinter.sol#L199
Owner of the contract is able to leave with all the tokens and ETH of the contract, which makes protocol trustless
You have implemented a function to
function recoverEther(uint256 amount) external onlyByOwnGov { (bool success,) = address(owner).call{ value: amount }(""); require(success, "Invalid transfer"); emit EmergencyEtherRecovered(amount); }
First of all this is a direct bypass of the
function moveWithheldETH(address payable to, uint256 amount) external onlyByOwnGov { require(amount <= currentWithheldETH, "Not enough withheld ETH in contract"); currentWithheldETH -= amount; (bool success,) = payable(to).call{ value: amount }(""); require(success, "Invalid transfer"); emit WithheldETHMoved(to, amount); }
So in any case a bypass of this is just a high risk.
Moreover, it is not a good idea to have any kind of emergencyRecovery . It is correct to be sure that your protocol will never have ETH stucked by its logic. This is why we are here ! :). Otherwise anybody will implement the same solution and we will never be worried about stucked ETH.
A similar issue here
function recoverERC20(address tokenAddress, uint256 tokenAmount) external onlyByOwnGov { require(IERC20(tokenAddress).transfer(owner, tokenAmount), "recoverERC20: Transfer failed"); emit EmergencyERC20Recovered(tokenAddress, tokenAmount); }
​ In other protocols this function generally is limited to all tokens except those handled by the protocol. You need to be sure that those ERC20 are not frxETHToken or sfrxToken.
Remove recoverEther()
function
And for recoverERC20()
be sure to check that
[+] require(tokenAddress != address(frxETHToken), "Some message") [+] require(tokenAddress != address(sfrxETHToken), "Some message")
#0 - FortisFortuna
2022-09-25T21:13:24Z
We are well aware of the permission structure. The owner will most likely be a large multisig. We mentioned the Frax Multisig in the scope too.
#1 - joestakey
2022-09-26T18:16:21Z
Duplicate of #107
🌟 Selected for report: Lambda
Also found by: 0x1f8b, 0x5rings, 0xSky, 0xSmartContract, 8olidity, Chom, CodingNameKiki, IllIllI, Ruhum, Sm4rty, brgltd, hansfriese, m9800, magu, pashov, pedroais, peritoflores, prasantgupta52, rokinot, seyni
12.4859 USDC - $12.49
Contract frxETHMinter is unable to recover tokens like USDT
Tokens that return void
on transfer
, that is, those who do not follow ERC20 standard will revert when you try to assign the output to a boolean variable.
This is the case in you function recoverERC20
I believe that it is better just to remove require
and check balance off-chain.
Otherwise, you will need libraries like SafeTransfer but I don't think that this is really neccessary. Tokens return false generally when you try to transfer more balance that you have or you are not approved.
[-] require(IERC20(tokenAddress).transfer(owner, tokenAmount), "recoverERC20: Transfer failed"); [+] IERC20(tokenAddress).transfer(owner, tokenAmount);
#0 - FortisFortuna
2022-09-25T21:28:55Z
Not really medium risk. Technically you could use safeTransfer, but if someone were to accidentally send something to this contract, it would most likely be either ETH, FRAX, frxETH, or sfrxETH, all of which are transfer compliant.
#1 - joestakey
2022-09-26T17:05:15Z
Duplicate of #18
🌟 Selected for report: rotcivegaf
Also found by: 0x040, 0x1f8b, 0x4non, 0xNazgul, 0xSmartContract, 0xf15ers, 8olidity, Aymen0909, B2, Bahurum, Bnke0x0, Ch_301, CodingNameKiki, Deivitto, Diana, Funen, IllIllI, JC, JLevick, KIntern_NA, Lambda, OptimismSec, PaludoX0, RockingMiles, Rolezn, Sm4rty, Soosh, Tagir2003, Tointer, TomJ, Triangle, Trust, V_B, Waze, Yiko, __141345__, a12jmx, ajtra, asutorufos, ayeslick, aysha, bbuddha, bharg4v, bobirichman, brgltd, bytera, c3phas, cryptostellar5, cryptphi, csanuragjain, datapunk, delfin454000, durianSausage, exd0tpy, gogo, got_targ, jag, joestakey, karanctf, ladboy233, leosathya, lukris02, mics, millersplanet, natzuu, neko_nyaa, obront, oyc_109, parashar, peritoflores, rbserver, ret2basic, rokinot, ronnyx2017, rvierdiiev, sach1r0, seyni, sikorico, slowmoses, tnevler, yasir, yongskiws
28.0141 USDC - $28.01
​ Risk of reentrancy in submitAndDeposit
function.
​ I see that you added the non reentrant modifier to the internal function _submit()
.
The problem with this is that you are not protecting some parts of the function submitAndDeposit()
function submitAndDeposit(address recipient) external payable returns (uint256 shares) { // Give the frxETH to this contract after it is generated _submit(address(this)); // Approve frxETH to sfrxETH for staking frxETHToken.approve(address(sfrxETHToken), msg.value); // Deposit the frxETH and give the generated sfrxETH to the final recipient uint256 sfrxeth_recieved = sfrxETHToken.deposit(msg.value, recipient); require(sfrxeth_recieved > 0, 'No sfrxETH was returned'); return sfrxeth_recieved; }
In this case if sfrxETHToken.deposit(msg.value, recipient);
it could reenter depending on implementation (actually we only have access to interface IsfrxETH
)
In any case it is better to add reentrant modifier to every external function that you want to protect.
Remove nonReentrant
modifier from _submit()
and add it to every function that uses it
#0 - FortisFortuna
2022-09-25T23:50:42Z
Was anything brickable or could funds be stolen? We saw this with Slither and deemed it a non-issue.
#1 - itsmetechjay
2022-09-28T16:00:31Z
@peritoflores, can you chime in on the sponsor question above?
#2 - peritoflores
2022-09-28T19:36:43Z
Hi @FortisFortuna ,
I reported this issue as "medium" because according to code4rena standard.
"Assets not at direct risk, but the function of the protocol or its availability could be impacted, or leak value with a hypothetical attack path with stated assumptions, but external requirements."
The main problem is that sfrxETHToken is just an interface so I do not have the code to say that is where the "hypotethical" is. In any case it is pretty general that all the functions in most of the protocols that are related to "deposit" or "withdraw" are always protected by non-reentrant modifier, unless you have no any external call which is not your case. @itsmetechjay
#3 - 0xean
2022-10-12T16:56:28Z
Downgrading to QA due to lack of more explicit POC. The possibility of re-entrancy alone is not enough for M