Platform: Code4rena
Start Date: 01/09/2023
Pot Size: $36,500 USDC
Total HM: 4
Participants: 70
Period: 6 days
Judge: kirk-baird
Id: 281
League: ETH
Rank: 44/70
Findings: 2
Award: $16.83
š Selected for report: 0
š Solo Findings: 0
š Selected for report: adriro
Also found by: 0x6980, 0xStalin, 0xanmol, 0xmystery, 0xpanicError, Arz, Aymen0909, BenRai, Breeje, Lalanda, MohammedRizwan, Raihan, SovaSlava, Stormreckson, Udsen, ast3ros, bin2chen, castle_chain, catellatech, codegpt, dev0cloo, gkrastenov, hals, klau5, kutugu, ladboy233, matrix_0wl, nirlin, ohm, peanuts, pipidu83, sandy, wahedtalash77
7.08 USDC - $7.08
Number | Issue | Instances | |
---|---|---|---|
[Lā01] | The contracts has used outdated version of openzeppelin library | contracts | |
[Lā02] | stale or misleading comment in rUSDYFactory.sol | 1 | |
[Lā03] | Multicall being payable is seriously dangerous | 1 | |
[Lā04] | Calls inside loops that may address DoS | 1 |
From package.json, It is confirmed that the contracts has used "@openzeppelin/contracts": "^4.8.3",
which is an old version and has security issues. While using external libraries, Ensure it is of latest version.
Update the openzeppelin version to v4.9.3
rUSDYFactory.sol
In rUSDYFactory.sol
, There is misleading or stale comment. When asked about it sponsor made it stale Natspec.
ii) Revoke the `MINTER_ROLE`, `PAUSER_ROLE` & `DEFAULT_ADMIN_ROLE` from address(this).
Remove the stale Natspec which are no longer needed in contract to prevent misleading.
multiexcall()
being payable is seriously dangerousIn rUSDYFactory.sol
, multiexcall()
allows for arbitrary batched calls but it is made payable which will cause issues since address(exCallData[i].target).call
is in a loop, it is not advisable to add payable to the existing call(). This is because msg.value may be used multiple times.
There is 1 instance of this issue:
File: contracts/usdy/rUSDYFactory.sol function multiexcall( ExCallData[] calldata exCallData ) external payable override onlyGuardian returns (bytes[] memory results) { results = new bytes[](exCallData.length); for (uint256 i = 0; i < exCallData.length; ++i) { (bool success, bytes memory ret) = address(exCallData[i].target).call{ value: exCallData[i].value }(exCallData[i].data); require(success, "Call Failed"); results[i] = ret; } }
Current implementation is not recommended, Howeve this discussion is helpful to mitigate the issue.
In rUSDYFactory.sol
, multiexcall()
function,
Calls to external contracts inside a loop are dangerous because it could lead to DoS if one of the calls reverts or execution runs out of gas. Such issue also introduces chance of problems with the gas limits.
Per SWC-113: External calls can fail accidentally or deliberately, which can cause a DoS condition in the contract. To minimize the damage caused by such failures, it is better to isolate each external call into its own transaction that can be initiated by the recipient of the call.
Reference link- https://swcregistry.io/docs/SWC-113
There is 1 instance of this issue:
File: contracts/usdy/rUSDYFactory.sol function multiexcall( ExCallData[] calldata exCallData ) external payable override onlyGuardian returns (bytes[] memory results) { results = new bytes[](exCallData.length); for (uint256 i = 0; i < exCallData.length; ++i) { (bool success, bytes memory ret) = address(exCallData[i].target).call{ value: exCallData[i].value }(exCallData[i].data); require(success, "Call Failed"); results[i] = ret; } }
#0 - c4-pre-sort
2023-09-08T08:35:43Z
raymondfam marked the issue as sufficient quality report
#1 - c4-judge
2023-09-21T10:15:44Z
kirk-baird marked the issue as grade-b
š Selected for report: c3phas
Also found by: 0x11singh99, 0xhex, 0xta, Eurovickk, K42, MohammedRizwan, SAAJ, SAQ, SY_S, adriro, albahaca, castle_chain, jeffy, kaveyjoe, matrix_0wl, naman1778, petrichor, wahedtalash77, ybansal2403, zabihullahazadzoi
9.7506 USDC - $9.75
Issue | Instances | ||
---|---|---|---|
[Gā01] | _rmul() can gas optimized by removing extra lines of code | 1 | |
[Gā02] | Use assembly to emit events | 1 | |
[Gā03] | Catch arguments as local variables in simulateRange() | 1 | |
[Gā04] | Catch value in roundUpTo8() | 1 | |
[Gā05] | save gas by using 10e27 instead 10 ** 27 | 1 | |
[Gā06] | _rpow() and _rmul() can be imported instead of hardcoding it | 1 | |
[Gā07] | Short the string message in onlyGuardian to save 256 bit storage | 1 |
_rmul()
can gas optimized by removing extra lines of code_rmul()
has been used in derivePrice()
. With current implementation it has added extra bytes resulting in more deployment cost. This can be further gas optimized per recommendation.
There is 1 instance of this issue:
File: contracts/rwaOracles/RWADynamicOracle.sol function _rmul(uint256 x, uint256 y) internal pure returns (uint256 z) { z = _mul(x, y) / ONE; } function _mul(uint256 x, uint256 y) internal pure returns (uint256 z) { require(y == 0 || (z = x * y) / y == x); }
function _rmul(uint256 x, uint256 y) internal pure returns (uint256 z) { - z = _mul(x, y) / ONE; + z = x * y; + require(y == 0 || z / y == x); + z = z / ONE; } - function _mul(uint256 x, uint256 y) internal pure returns (uint256 z) { - require(y == 0 || (z = x * y) / y == x); - }
Assembly can be used to emit events efficiently by utilizing scratch space and the free memory pointer. This will allow us to potentially avoid memory expansion costs. Note: In order to do this optimization safely, we will need to cache and restore the free memory pointer.
There are 2 instances of this issue:
File: contracts/rwaOracles/RWADynamicOracle.sol event RangeSet( uint256 indexed index, uint256 start, uint256 end, uint256 dailyInterestRate, uint256 prevRangeClosePrice ); event RangeOverriden( uint256 indexed index, uint256 newStart, uint256 newEnd, uint256 newDailyInterestRate, uint256 newPrevRangeClosePrice );
simulateRange()
Caching will replace each Gwarmaccess (100 gas) with a much cheaper stack read.
There is 1 instance of this issue:
File: contracts/rwaOracles/RWADynamicOracle.sol function simulateRange( uint256 blockTimeStamp, uint256 dailyIR, uint256 endTime, uint256 startTime, uint256 rangeStartPrice ) external view returns (uint256 price) {
Catch function arguments as local variables.
value
in roundUpTo8()
Caching will replace each Gwarmaccess (100 gas) with a much cheaper stack read.
function roundUpTo8(uint256 value) internal pure returns (uint256) { + uint256 _value = value; - uint256 remainder = value % 1e10; + uint256 remainder = _value % 1e10; if (remainder >= 0.5e10) { - value += 1e10; + _value += 1e10; } - value -= remainder; + _value -= remainder; - return value; + _return value; }
The compiler needs to do extra computation while fetching the value 10 ** 27. However this can be optimized by using 10e27.
There is 1 instance of this issue:
- uint256 private constant ONE = 10 ** 27; + uint256 private constant ONE = 10e27;
_rpow()
and _rmul()
can be imported instead of hardcoding it_rpow()
and _rmul()
has been taken from makeDao contract, However these functions can be added in library instead of hardcoding it in contract. This will eliminate extra bytes and will make the contract simpler.
onlyGuardian
to save 256 bit storageEvery character of string message count as 1 byte. Reduce it to save some gas.
modifier onlyGuardian() { - require(msg.sender == guardian, "rUSDYFactory: You are not the Guardian"); + require(msg.sender == guardian, "rUSDYFactory: Not Guardian"); _; }
#0 - c4-pre-sort
2023-09-08T14:38:51Z
raymondfam marked the issue as sufficient quality report
#1 - c4-judge
2023-09-24T06:10:08Z
kirk-baird marked the issue as grade-b