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: 43/69
Findings: 1
Award: $52.84
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: obront
Also found by: 8olidity, HE1M, Madalad, Trust, cccz, csanuragjain, deliriusz, hansfriese, hihen, joestakey, rvierdiiev, wait, zaskoh
52.8446 USDC - $52.84
Having separate roles for each critical task, is a good security action. But, the number of roles define the level of security. In this protocol, if three roles are malicious, they can drain the protocol.
The three roles SET_MANAGER_ROLE
, MANAGER_WITHDRAW_ROLE
, and SET_MIN_RESERVE_PERCENTAGE_ROLE
can drain all the fund in the collateral reserve, if they are compromised or act maliciously.
The flow is:
SET_MANAGER_ROLE
changes the manager address.function setManager(address _newManager) external override onlyRole(SET_MANAGER_ROLE) { manager = _newManager; emit ManagerChange(_newManager); }
SET_MIN_RESERVE_PERCENTAGE_ROLE
, sets the minReservePercentage
equal to zero.function setMinReservePercentage(uint256 _newMinReservePercentage) external override onlyRole(SET_MIN_RESERVE_PERCENTAGE_ROLE) { require(_newMinReservePercentage <= PERCENT_DENOMINATOR, ">100%"); minReservePercentage = _newMinReservePercentage; emit MinReservePercentageChange(_newMinReservePercentage); }
MANAGER_WITHDRAW_ROLE
withdraws all fund from collateral reserve to the new malicious manager. Since, minReservePercentage
was set to zero in step two, getMinReserve()
will return zero. So, the condition require(collateral.getReserve() - _amountAfterFee >= getMinReserve(), "reserve would fall below minimum");
allow to drain all the reserve.function managerWithdraw(uint256 _amount) external override onlyRole(MANAGER_WITHDRAW_ROLE) nonReentrant { if (address(managerWithdrawHook) != address(0)) managerWithdrawHook.hook(msg.sender, _amount, _amount); baseToken.transfer(manager, _amount); }
It is recommended to define another role who sets the limit for the minimum value of minReservePercentage
.
function setMinReservePercentage(uint256 _newMinReservePercentage) external override onlyRole(SET_MIN_RESERVE_PERCENTAGE_ROLE) { require(_newMinReservePercentage <= PERCENT_DENOMINATOR, ">100%"); require((_newMinReservePercentage * 100)/PERCENT_DENOMINATOR >= minReserveLimit, "withdrawing more than allowed"); minReservePercentage = _newMinReservePercentage; emit MinReservePercentageChange(_newMinReservePercentage); } function setMinReserveLimit(uint256 _newMinReserveLimit) external override onlyRole(SET_MIN_RESERVE_LIMIT_ROLE) { minReserveLimit = _newMinReserveLimit; emit MinReserveLimitChange(_newMinReserveLimit); }
Or add the condition require((_newMinReservePercentage * 100)/PERCENT_DENOMINATOR >= 50, "withdrawing more than 50%");
to the function setMinReservePercentage
, to not allow withdrawing more than 50%.
#0 - hansfriese
2022-12-14T18:05:06Z
duplicate of #254
#1 - c4-judge
2022-12-17T10:02:00Z
Picodes marked the issue as duplicate of #254
#2 - c4-judge
2023-01-01T17:42:28Z
Picodes marked the issue as satisfactory