Platform: Code4rena
Start Date: 29/03/2024
Pot Size: $36,500 USDC
Total HM: 5
Participants: 72
Period: 5 days
Judge: 3docSec
Total Solo HM: 1
Id: 357
League: ETH
Rank: 72/72
Findings: 1
Award: $8.28
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: immeas
Also found by: 0xAkira, 0xCiphky, 0xGreyWolf, 0xJaeger, 0xMosh, 0xabhay, 0xlemon, 0xmystery, 0xweb3boy, Aamir, Abdessamed, Aymen0909, Breeje, DanielArmstrong, DarkTower, Dots, EaglesSecurity, FastChecker, HChang26, Honour, IceBear, JC, K42, Krace, MaslarovK, Omik, OxTenma, SAQ, Shubham, Stormreckson, Tigerfrake, Tychai0s, VAD37, ZanyBonzy, albahaca, arnie, ast3ros, asui, b0g0, bareli, baz1ka, btk, caglankaan, carrotsmuggler, cheatc0d3, dd0x7e8, grearlake, igbinosuneric, jaydhales, kaden, kartik_giri_47538, m4ttm, ni8mare, niser93, nonn_ac, oualidpro, pfapostol, pkqs90, popeye, radev_sw, samuraii77, slvDev, zabihullahazadzoi
8.2807 USDC - $8.28
In the ROUSG
contract, the burn
function allows an address with the BURNER_ROLE
role to directly burn rOUSG tokens of a specified user. However, the corresponding OUSG tokens that should be exchanged as compensation for the burnt rOUSG are not transferred to the user; instead, they are given to the caller. This results in a loss for the user.
Here are a few possible reasons why the contract might be designed with this feature:
Emergency Situations: If the rOUSG system encounters a critical security issue or vulnerability, there may be a need to urgently stop the circulation of tokens and reclaim them. In such cases, an authorized administrator or guardian might require the authority to destroy tokens to protect user assets or maintain system stability.
Compliance or Regulatory Requirements: If a user's funds are of unclear origin or suspected of illegal activities, there might be a need for the capability to freeze or confiscate these funds. This design allows contract administrators to take action when compliance or legal requirements necessitate it.
Error Correction: If users incorrectly receive rOUSG tokens due to some error, there may need to be a mechanism to correct this, which might involve destroying tokens that should not have been distributed.
However, this design does indeed bring risks and potential for abuse, as administrators or roles with the burning privilege could misuse this function to deprive users of their assets, leading to a loss of trust and compromising the overall security of the system.
In summary, while the burn
method may have its specific uses, it must be implemented cautiously, ensuring that sufficient safeguards are in place to prevent abuse and protect the interests of users.
getOUSGPrice
of ROUSG
ContractThere is indeed an inconsistency in the getOUSGPrice
function between the two contracts, OUSGInstantManager
and ROUSG
.
OUSGInstantManager
Contract:The getOUSGPrice
function in the OUSGInstantManager
contract includes a safety check to ensure that the price returned by the oracle is not unexpectedly low:
function getOUSGPrice() public view returns (uint256 price) { (price, ) = oracle.getPriceData(); require( price > MINIMUM_OUSG_PRICE, "OUSGInstantManager::getOUSGPrice: Price unexpectedly low" ); }
This check ensures that the price is above a defined MINIMUM_OUSG_PRICE
, which acts as a circuit breaker in case of oracle malfunction or other unexpected behaviors that could result in a price that is too low, potentially leading to adverse effects on the contract's economics.
ROUSG
Contract:In contrast, the getOUSGPrice
function in the ROUSG
contract does not perform any safety checks on the price returned by the oracle:
function getOUSGPrice() public view returns (uint256 price) { (price, ) = oracle.getPriceData(); }
This version of the function simply returns the price provided by the oracle without any additional validation. As a result, if the oracle were to return a price that is too low (or even zero), the contract would accept it as is, which could lead to incorrect calculations of token balances, shares, and potentially other critical financial operations within the contract.
The inconsistency in the getOUSGPrice
function between the two contracts could lead to different behaviors and security postures. In OUSGInstantManager
, the system is protected against extremely low oracle prices, while in ROUSG
, it is not.
In scenarios where an oracle malfunction or attack results in an artificially low price, the ROUSG
contract could incorrectly calculate token balances and shares, leading to significant financial losses or allowing for exploits such as the unwrapping of tokens at incorrect rates.
To resolve this inconsistency, it would be prudent to align the getOUSGPrice
function across both contracts by implementing the same safety checks. This would ensure that both contracts operate under the same assumptions and protections regarding the price data they rely on.
whenNotPaused
Modifier in ROUSG
ContractIn the rOUSG
contract, the whenNotPaused
modifier is used to prevent certain functions from being executed when the contract is paused.
In the provided snippet, the whenNotPaused
modifier is applied to the _mintShares
and _burnShares
internal functions:
function _mintShares( address _recipient, uint256 _sharesAmount ) internal whenNotPaused returns (uint256) { // ... implementation }
function _burnShares( address _account, uint256 _sharesAmount ) internal whenNotPaused returns (uint256) { // ... implementation }
This internal function appears to be called by the external functions, which also have the whenNotPaused
modifier applied:
The redundancy here is that the check for whether the contract is paused is being performed twice: once in the wrap
function and once in the _mintShares
internal function that wrap
calls. This is unnecessary and could be considered inefficient because the state of the contract (paused or not) would not change within the same transaction, and checking it twice does not provide additional security or functional benefits.
It's generally a best practice to apply such modifiers at the highest level of publicly accessible functions (i.e., external or public functions that are part of the contract's interface) rather than on internal functions. This approach minimizes the number of checks and gas costs associated with function calls while maintaining the intended security constraints.
Therefore, to improve gas efficiency and avoid redundancy, the whenNotPaused
modifier should only be applied to the external functions and not to the internal functions. The external functions serve as the entry point for the transaction, and once the contract state is checked and validated at this point, all subsequent internal calls within the same transaction can be considered safe to proceed without additional pause checks.
#0 - c4-pre-sort
2024-04-05T04:12:10Z
0xRobocop marked the issue as sufficient quality report
#1 - c4-judge
2024-04-10T07:20:56Z
3docSec marked the issue as grade-b