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: 54/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
Issue | Instances | |
---|---|---|
[L‑1] | Vulnerable versions of packages are being used | 1 |
[L‑2] | For loops in public or external functions should be avoided due to high gas costs and possible DOS | 2 |
[L‑3] | External call recipient may consume all transaction gas (gas griefing) | 2 |
[L‑4] | Constant decimal values | 11 |
[L‑5] | increase/decrease or forceApprove allowance should be used instead of approve | 2 |
[L‑6] | Loss of precision | 2 |
[L‑7] | Missing contract-existence checks before low-level calls | 2 |
[L‑8] | Governance operations should be behind a timelock | 28 |
[L‑9] | Large transfers may not work with some ERC20 tokens | 1 |
[L‑10] | Consider implementing two-step procedure for updating protocol addresses | 1 |
[L‑11] | Upgradeable contract is missing a __gap[50] storage variable to allow for new storage variables in later versions | 1 |
[L‑12] | Check of division by zero | 1 |
[L‑13] | Some function should not be marked as payable | 2 |
[L‑14] | prevent re-setting a state variable with the same value | 15 |
Total: 71 instances over 14 issues
Issue | Instances | |
---|---|---|
[NC‑1] | [NatSpec] Natspec @author is missing from contract | 1 |
[NC‑2] | Constant redefined elsewhere | 3 |
[NC‑3] | Constants in comparisons should appear on the left side | 16 |
[NC‑4] | [NatSpec] Natspec description is missing from contract | 49 |
[NC‑5] | Control structures do not follow the Solidity Style Guide | 44 |
[NC‑6] | Custom error has no error details | 1 |
[NC‑7] | Consider adding emergency-stop functionality | 1 |
[NC‑8] | Events are missing sender information | 18 |
[NC‑9] | Defining All External/Public Functions in Contract Interfaces | 20 |
[NC‑10] | Duplicated require() checks should be refactored to a modifier or function | 1 |
[NC‑11] | Imports could be organized more systematically | 2 |
[NC‑12] | Import declarations should import specific identifiers, rather than the whole file | 23 |
[NC‑13] | Inconsistent spacing in comments | 1 |
[NC‑14] | Inconsistent usage of require /error | 2 |
[NC‑15] | Long functions should be refactored into multiple, smaller, functions | 3 |
[NC‑16] | Long lines of code | 15 |
[NC‑17] | Consider using later versions of solidity for more cappabilities | 3 |
[NC‑18] | Some error strings are not descriptive | 2 |
[NC‑19] | Use of override is unnecessary | 20 |
[NC‑20] | Redundant inheritance specifier | 3 |
[NC‑21] | Setters should prevent re-setting of the same value | 6 |
[NC‑22] | Consider using SafeTransferLib.safeTransferETH() or Address.sendValue() for clearer semantic meaning | 2 |
[NC‑23] | Large multiples of ten should use scientific notation (e.g. 1e6 ) rather than decimal literals (e.g. 1000000 ), for readability | 3 |
[NC‑24] | Consider moving msg.sender checks to a common authorization modifier | 3 |
[NC‑25] | State variables should have Natspec comments | 34 |
[NC‑26] | Contracts should have full test coverage | 1 |
[NC‑27] | Top level pragma declarations should be separated by two blank lines | 2 |
[NC‑28] | Critical functions should be a two step procedure | 15 |
[NC‑29] | Typos | 2 |
[NC‑30] | Unused Import | 2 |
[NC‑31] | Missing upgradability functionality | 1 |
[NC‑32] | Use the latest solidity (prior to 0.8.20 if on L2s) for deployment | 3 |
[NC‑33] | Use a single file for system wide constants | 3 |
[NC‑34] | Consider using SMTChecker | 3 |
[NC‑35] | Whitespace in Expressions | 12 |
[NC‑36] | Consider bounding input array length | 2 |
[NC‑37] | [NatSpec] Natspec @dev is missing from contract | 55 |
[NC‑38] | Add inline comments for unnamed variables | 5 |
[NC‑39] | Contract should expose an interface | 19 |
[NC‑40] | Named imports of parent contracts are missing | 13 |
[NC‑41] | [NatSpec] Natspec @notice is missing from contract | 30 |
[NC‑42] | Event names should use CamelCase | 1 |
[NC‑43] | Expressions for constant values should use immutable rather than constant | 6 |
[NC‑44] | Top-level declarations should be separated by at least two lines | 3 |
[NC‑45] | Contract uses both require() /revert() as well as custom errors | 2 |
[NC‑46] | Consider using AccessControlDefaultAdminRules rather than AccessControl | 1 |
[NC‑47] | immutable variable names don't follow the Solidity style guide | 8 |
[NC‑48] | Add inline comments for unnamed parameters | 14 |
[NC‑49] | Use the latest Solidity version for better security | 3 |
[NC‑50] | Consider adding formal verification proofs | 1 |
[NC‑51] | Missing zero address check in functions with address parameters | 3 |
[NC‑52] | Use a struct to encapsulate multiple function parameters | 4 |
[NC‑53] | Use custom errors rather than revert() /require() strings for better readability | 50 |
[NC‑54] | Use @inheritdoc for overridden functions | 20 |
[NC‑55] | Multiple mappings with same keys can be combined into a single struct mapping for readability | 2 |
[NC‑56] | constructor should emit an event | 3 |
[NC‑57] | Complex functions should include comments | 3 |
[NC‑58] | [Solidity]: All verbatim blocks are considered identical by deduplicator and can incorrectly be unified | 3 |
[NC‑59] | [NatSpec] Natspec @param is missing from error | 31 |
[NC‑60] | OpenZeppelin libraries should be upgraded to a newer version | 11 |
[NC‑61] | Not using the latest version of prb-math from dependencies | 1 |
[NC‑62] | Enforcing Lowercase and Underscores Only in the name Field of package.json | 1 |
[NC‑63] | Consider making private state variables internal to increase flexibility | 3 |
[NC‑64] | Using Low-Level Call for Transfers | 2 |
[NC‑65] | A event should be emitted if a non immutable state variable is set in a constructor | 10 |
[NC‑66] | [Solidity]: Order of Argument Evaluation Disrupted in Non-Expression-Split Code by Optimizer Sequences with FullInliner | 3 |
[NC‑67] | Use named return values | 24 |
[NC‑68] | Consider moving duplicated strings to constants | 24 |
[NC‑69] | Missing checks for uint state variable assignments | 5 |
[NC‑70] | Consider adding validation of user inputs | 25 |
[NC‑71] | Consider disallowing transfers to address(this) | 3 |
[NC‑72] | Consider using named function calls | 28 |
Total: 742 instances over 72 issues
This project's specific package versions are vulnerable to the specific CVEs listed below. Consider switching to more recent versions of these packages that don't have these vulnerabilities
There are 1 instances of this issue:
<details> <summary>see instances</summary></details>File: package.json <details><summary>Vulnerabilities related to `@openzeppelin/contracts`:</summary> - [CVE-2023-34459](https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories/GHSA-wprv-93r4-jj2p) :When the verifyMultiProof, verifyMultiProofCalldata, processMultiProof, or processMultiProofCalldata functions are in use, it is possible to construct merkle trees that allow forging a valid multiproof for an arbitrary set of leaves. A contract may be vulnerable if it uses multiproofs for verification and the merkle tree that is processed includes a node with value 0 at depth 1(just under the root).This could happen inadvertently for balanced trees with 3 leaves or less, if the leaves are not hashed.This could happen deliberately if a malicious tree builder includes such a node in the tree. A contract is not vulnerable if it uses single- leaf proving(verify, verifyCalldata, processProof, or processProofCalldata), or if it uses multiproofs with a known tree that has hashed leaves.Standard merkle trees produced or validated with the @openzeppelin/merkle-tree library are safe. - [CVE-2023-34234](https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories/GHSA-5h3x-9wvq-w4m2) :By frontrunning the creation of a proposal, an attacker can become the proposer and gain the ability to cancel it. The attacker can do this repeatedly to try to prevent a proposal from being proposed at all. This impacts the Governor contract in v4.9.0 only, and the GovernorCompatibilityBravo contract since v4.3.0. </details> 1:
In Solidity, for loops can potentially cause Denial of Service (DoS) attacks if not handled carefully. DoS attacks can occur when an attacker intentionally exploits the gas cost of a function, causing it to run out of gas or making it too expensive for other users to call. Below are some scenarios where for loops can lead to DoS attacks: Nested for loops can become exceptionally gas expensive and should be used sparingly
There are 2 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 794: function multiexcall( 795: ExCallData[] calldata exCallData 796: ) 797: external 798: payable 799: override 800: onlyRole(DEFAULT_ADMIN_ROLE) 801: returns (bytes[] memory results) 802: { 803: results = new bytes[](exCallData.length); 804: for (uint256 i = 0; i < exCallData.length; ++i) {
</details>File: ./contracts/ousg/rOUSGFactory.sol 121: function multiexcall( 122: ExCallData[] calldata exCallData 123: ) external payable override onlyGuardian returns (bytes[] memory results) { 124: results = new bytes[](exCallData.length); 125: for (uint256 i = 0; i < exCallData.length; ++i) {
recipient
may consume all transaction gas (gas griefing)There is no limit specified on the amount of gas used, so the recipient can use up all of the transaction's gas, causing it to revert. Use addr.call{ gas: <amount>}("")
or this library instead.
There are 2 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 805: (bool success, bytes memory ret) = address(exCallData[i].target).call{
</details>File: ./contracts/ousg/rOUSGFactory.sol 126: (bool success, bytes memory ret) = address(exCallData[i].target).call{
The use of fixed decimal values such as 1e18 or 1e8 in Solidity contracts can lead to inaccuracies, bugs, and vulnerabilities, particularly when interacting with tokens having different decimal configurations.Not all ERC20 tokens follow the standard 18 decimal places, and assumptions about decimal places can lead to miscalculations.
Always retrieve and use the decimals()
function from the token contract itself when performing calculations involving token amounts.This ensures that your contract correctly handles tokens with any number of decimal places, mitigating the risk of numerical errors or under / overflows that could jeopardize contract integrity and user funds.
There are 11 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 689: uint256 amountE36 = _scaleUp(usdcAmountIn) * 1e18; 704: usdcOwed = _scaleDown(amountE36 / 1e18);
File: ./contracts/ousg/rOUSG.sol 190: (totalShares * getOUSGPrice()) / (1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER); 190: (totalShares * getOUSGPrice()) / (1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER); 202: (1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER); 202: (1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER); 367: (_rOUSGAmount * 1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER) / getOUSGPrice(); 367: (_rOUSGAmount * 1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER) / getOUSGPrice(); 367: (_rOUSGAmount * 1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER) / getOUSGPrice(); 375: (_shares * getOUSGPrice()) / (1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER); 375: (_shares * getOUSGPrice()) / (1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER);
link , link, link, link, link, link, link, link, link
</details>In order to prevent front running, increase/decrease or forceApprove allowance should be used in place of approve where possible
There are 2 instances of this issue:
<details> <summary>see instances</summary></details>File: ./contracts/ousg/ousgInstantManager.sol 264: ousg.approve(address(rousg), ousgAmountOut); 464: buidl.approve(address(buidlRedeemer), buidlAmountToRedeem);
Division by large numbers may result in the result being zero, due to solidity not supporting fractions. Consider requiring a minimum amount for the numerator to ensure that it is always larger than the denominator
There are 2 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 690: ousgAmountOut = amountE36 / price;
</details>File: ./contracts/ousg/rOUSG.sol 367: (_rOUSGAmount * 1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER) / getOUSGPrice();
Low-level calls return success if there is no code present at the specified address. In addition to the zero-address checks, add a check to verify that <address>.code.length > 0
There are 2 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 805: (bool success, bytes memory ret) = address(exCallData[i].target).call{ 806: value: exCallData[i].value
</details>File: ./contracts/ousg/rOUSGFactory.sol 126: (bool success, bytes memory ret) = address(exCallData[i].target).call{ 127: value: exCallData[i].value
All critical and governance operations should be protected by a timelock. For example from the point of view of a user, the changing of the owner of a contract is a high risk operation that may have outcomes ranging from an attacker gaining control over the protocol, to the function no longer functioning due to a typo in the destination address. To give users plenty of warning so that they can validate any ownership changes, changes of ownership should be behind a timelock.
There are 28 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 498: function setInstantMintLimit( 499: uint256 _instantMintLimit 500: ) external override onlyRole(CONFIGURER_ROLE) { 501: _setInstantMintLimit(_instantMintLimit); 502: } 512: function setInstantRedemptionLimit( 513: uint256 _instantRedemptionLimit 514: ) external override onlyRole(CONFIGURER_ROLE) { 515: _setInstantRedemptionLimit(_instantRedemptionLimit); 516: } 526: function setInstantMintLimitDuration( 527: uint256 _instantMintLimitDuration 528: ) external override onlyRole(CONFIGURER_ROLE) { 529: _setInstantMintLimitDuration(_instantMintLimitDuration); 530: } 540: function setInstantRedemptionLimitDuration( 541: uint256 _instantRedemptionLimitDuratioin 542: ) external override onlyRole(CONFIGURER_ROLE) { 543: _setInstantRedemptionLimitDuration(_instantRedemptionLimitDuratioin); 544: } 554: function setMintFee( 555: uint256 _mintFee 556: ) external override onlyRole(CONFIGURER_ROLE) { 557: require(mintFee < 200, "OUSGInstantManager::setMintFee: Fee too high"); 558: emit MintFeeSet(mintFee, _mintFee); 559: mintFee = _mintFee; 560: } 567: function setRedeemFee( 568: uint256 _redeemFee 569: ) external override onlyRole(CONFIGURER_ROLE) { 570: require(redeemFee < 200, "OUSGInstantManager::setRedeemFee: Fee too high"); 571: emit RedeemFeeSet(redeemFee, _redeemFee); 572: redeemFee = _redeemFee; 573: } 581: function setMinimumDepositAmount( 582: uint256 _minimumDepositAmount 583: ) external override onlyRole(CONFIGURER_ROLE) { 584: require( 585: _minimumDepositAmount >= FEE_GRANULARITY, 586: "setMinimumDepositAmount: Amount too small" 587: ); 588: 589: emit MinimumDepositAmountSet(minimumDepositAmount, _minimumDepositAmount); 590: minimumDepositAmount = _minimumDepositAmount; 591: } 599: function setMinimumRedemptionAmount( 600: uint256 _minimumRedemptionAmount 601: ) external override onlyRole(CONFIGURER_ROLE) { 602: require( 603: _minimumRedemptionAmount >= FEE_GRANULARITY, 604: "setMinimumRedemptionAmount: Amount too small" 605: ); 606: emit MinimumRedemptionAmountSet( 607: minimumRedemptionAmount, 608: _minimumRedemptionAmount 609: ); 610: minimumRedemptionAmount = _minimumRedemptionAmount; 611: } 622: function setMinimumBUIDLRedemptionAmount( 623: uint256 _minimumBUIDLRedemptionAmount 624: ) external override onlyRole(DEFAULT_ADMIN_ROLE) { 625: emit MinimumBUIDLRedemptionAmountSet( 626: minBUIDLRedeemAmount, 627: _minimumBUIDLRedemptionAmount 628: ); 629: minBUIDLRedeemAmount = _minimumBUIDLRedemptionAmount; 630: } 638: function setOracle( 639: address _oracle 640: ) external override onlyRole(DEFAULT_ADMIN_ROLE) { 641: emit OracleSet(address(oracle), _oracle); 642: oracle = IRWAOracle(_oracle); 643: } 650: function setFeeReceiver( 651: address _feeReceiver 652: ) external override onlyRole(DEFAULT_ADMIN_ROLE) { 653: require(_feeReceiver != address(0), "FeeReceiver cannot be 0x0"); 654: emit FeeReceiverSet(feeReceiver, _feeReceiver); 655: feeReceiver = _feeReceiver; 656: } 663: function setInvestorBasedRateLimiter( 664: address _investorBasedRateLimiter 665: ) external override onlyRole(DEFAULT_ADMIN_ROLE) { 666: emit InvestorBasedRateLimiterSet( 667: address(investorBasedRateLimiter), 668: _investorBasedRateLimiter 669: ); 670: investorBasedRateLimiter = IInvestorBasedRateLimiter( 671: _investorBasedRateLimiter 672: ); 673: } 768: function pauseMint() external onlyRole(PAUSER_ROLE) { 769: mintPaused = true; 770: emit MintPaused(); 771: } 774: function unpauseMint() external onlyRole(DEFAULT_ADMIN_ROLE) { 775: mintPaused = false; 776: emit MintUnpaused(); 777: } 780: function pauseRedeem() external onlyRole(PAUSER_ROLE) { 781: redeemPaused = true; 782: emit RedeemPaused(); 783: } 786: function unpauseRedeem() external onlyRole(DEFAULT_ADMIN_ROLE) { 787: redeemPaused = false; 788: emit RedeemUnpaused(); 789: } 794: function multiexcall( 795: ExCallData[] calldata exCallData 796: ) 797: external 798: payable 799: override 800: onlyRole(DEFAULT_ADMIN_ROLE) 801: returns (bytes[] memory results) 802: { 803: results = new bytes[](exCallData.length); 804: for (uint256 i = 0; i < exCallData.length; ++i) { 805: (bool success, bytes memory ret) = address(exCallData[i].target).call{ 806: value: exCallData[i].value 807: }(exCallData[i].data); 808: require(success, "Call Failed"); 809: results[i] = ret; 810: } 811: } 819: function retrieveTokens( 820: address token, 821: address to, 822: uint256 amount 823: ) external onlyRole(DEFAULT_ADMIN_ROLE) { 824: IERC20(token).transfer(to, amount); 825: }
link , link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link
File: ./contracts/ousg/rOUSG.sol 112: function __rOUSG_init( 113: address _kycRegistry, 114: uint256 requirementGroup, 115: address _ousg, 116: address guardian, 117: address _oracle 118: ) internal onlyInitializing { 119: __rOUSG_init_unchained( 120: _kycRegistry, 121: requirementGroup, 122: _ousg, 123: guardian, 124: _oracle 125: ); 126: } 128: function __rOUSG_init_unchained( 129: address _kycRegistry, 130: uint256 _requirementGroup, 131: address _ousg, 132: address guardian, 133: address _oracle 134: ) internal onlyInitializing { 135: __KYCRegistryClientInitializable_init(_kycRegistry, _requirementGroup); 136: ousg = IERC20(_ousg); 137: oracle = IRWAOracle(_oracle); 138: _grantRole(DEFAULT_ADMIN_ROLE, guardian); 139: _grantRole(PAUSER_ROLE, guardian); 140: _grantRole(BURNER_ROLE, guardian); 141: _grantRole(CONFIGURER_ROLE, guardian); 142: } 613: function setOracle(address _oracle) external onlyRole(DEFAULT_ADMIN_ROLE) { 614: emit OracleSet(address(oracle), _oracle); 615: oracle = IRWAOracle(_oracle); 616: } 624: function burn( 625: address _account, 626: uint256 _amount 627: ) external onlyRole(BURNER_ROLE) { 628: uint256 ousgSharesAmount = getSharesByROUSG(_amount); 629: if (ousgSharesAmount < OUSG_TO_ROUSG_SHARES_MULTIPLIER) 630: revert UnwrapTooSmall(); 631: 632: _burnShares(_account, ousgSharesAmount); 633: 634: ousg.transfer( 635: msg.sender, 636: ousgSharesAmount / OUSG_TO_ROUSG_SHARES_MULTIPLIER 637: ); 638: emit Transfer(address(0), msg.sender, getROUSGByShares(ousgSharesAmount)); 639: emit TransferShares(_account, address(0), ousgSharesAmount); 640: } 642: function pause() external onlyRole(PAUSER_ROLE) { 643: _pause(); 644: } 646: function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) { 647: _unpause(); 648: } 650: function setKYCRegistry( 651: address registry 652: ) external override onlyRole(CONFIGURER_ROLE) { 653: _setKYCRegistry(registry); 654: } 656: function setKYCRequirementGroup( 657: uint256 group 658: ) external override onlyRole(CONFIGURER_ROLE) { 659: _setKYCRequirementGroup(group); 660: }
link , link, link, link, link, link, link, link
</details>File: ./contracts/ousg/rOUSGFactory.sol 070: function deployRebasingOUSG( 071: address kycRegistry, 072: uint256 requirementGroup, 073: address ousg, 074: address oracle 075: ) external onlyGuardian returns (address, address, address) { 076: require(!initialized, "ROUSGFactory: rOUSG already deployed"); 077: rOUSGImplementation = new ROUSG(); 078: rOUSGProxyAdmin = new ProxyAdmin(); 079: rOUSGProxy = new TokenProxy( 080: address(rOUSGImplementation), 081: address(rOUSGProxyAdmin), 082: "" 083: ); 084: ROUSG rOUSGProxied = ROUSG(address(rOUSGProxy)); 085: rOUSGProxied.initialize( 086: kycRegistry, 087: requirementGroup, 088: ousg, 089: guardian, 090: oracle 091: ); 092: 093: rOUSGProxyAdmin.transferOwnership(guardian); 094: assert(rOUSGProxyAdmin.owner() == guardian); 095: initialized = true; 096: emit rOUSGDeployed( 097: address(rOUSGProxy), 098: address(rOUSGProxyAdmin), 099: address(rOUSGImplementation), 100: rOUSGProxied.name(), 101: rOUSGProxied.symbol() 102: ); 103: return ( 104: address(rOUSGProxy), 105: address(rOUSGProxyAdmin), 106: address(rOUSGImplementation) 107: ); 108: } 121: function multiexcall( 122: ExCallData[] calldata exCallData 123: ) external payable override onlyGuardian returns (bytes[] memory results) { 124: results = new bytes[](exCallData.length); 125: for (uint256 i = 0; i < exCallData.length; ++i) { 126: (bool success, bytes memory ret) = address(exCallData[i].target).call{ 127: value: exCallData[i].value 128: }(exCallData[i].data); 129: require(success, "Call Failed"); 130: results[i] = ret; 131: } 132: }
ERC20
tokensSome IERC20
implementations (e.g UNI
, COMP
) may fail if the valued transferred is larger than uint96
. Source
There are 1 instances of this issue:
<details> <summary>see instances</summary></details>File: ./contracts/ousg/ousgInstantManager.sol 824: IERC20(token).transfer(to, amount);
A copy-paste error or a typo may end up bricking protocol functionality, or sending tokens to an address with no known private key. Consider implementing a two-step procedure for updating protocol addresses, where the recipient is set as pending, and must 'accept' the assignment by making an affirmative call. A straight forward way of doing this would be to have the target contracts implement EIP-165, and to have the 'set' functions ensure that the recipient is of the right interface type.
There are 1 instances of this issue:
<details> <summary>see instances</summary></details>File: ./contracts/ousg/ousgInstantManager.sol 650: function setFeeReceiver( 651: address _feeReceiver 652: ) external override onlyRole(DEFAULT_ADMIN_ROLE) { 653: require(_feeReceiver != address(0), "FeeReceiver cannot be 0x0"); 654: emit FeeReceiverSet(feeReceiver, _feeReceiver); 655: feeReceiver = _feeReceiver; 656: }
__gap[50]
storage variable to allow for new storage variables in later versionsSee this link for a description of this storage variable. While some contracts may not currently be sub-classed, adding the variable now protects against forgetting to add it in the future.
There are 1 instances of this issue:
<details> <summary>see instances</summary></details>File: ./contracts/ousg/rOUSG.sol 55: contract ROUSG is
There are 1 instances of this issue:
<details> <summary>see instances</summary></details>File: ./contracts/ousg/ousgInstantManager.sol 690: ousgAmountOut = amountE36 / price;
Some function should not be marked as payable, otherwise the ETH that mistakenly sent along with the function call is locked in the contract
There are 2 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 794: function multiexcall( 795: ExCallData[] calldata exCallData 796: ) 797: external 798: payable 799: override 800: onlyRole(DEFAULT_ADMIN_ROLE) 801: returns (bytes[] memory results) 802: { 803: results = new bytes[](exCallData.length); 804: for (uint256 i = 0; i < exCallData.length; ++i) { 805: (bool success, bytes memory ret) = address(exCallData[i].target).call{ 806: value: exCallData[i].value 807: }(exCallData[i].data); 808: require(success, "Call Failed"); 809: results[i] = ret; 810: } 811: }
</details>File: ./contracts/ousg/rOUSGFactory.sol 121: function multiexcall( 122: ExCallData[] calldata exCallData 123: ) external payable override onlyGuardian returns (bytes[] memory results) { 124: results = new bytes[](exCallData.length); 125: for (uint256 i = 0; i < exCallData.length; ++i) { 126: (bool success, bytes memory ret) = address(exCallData[i].target).call{ 127: value: exCallData[i].value 128: }(exCallData[i].data); 129: require(success, "Call Failed"); 130: results[i] = ret; 131: } 132: }
Not only is wasteful in terms of gas, but this is especially problematic when an event is emitted and the old and new values set are the same, as listeners might not expect this kind of scenario.
There are 15 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 498: function setInstantMintLimit( 512: function setInstantRedemptionLimit( 526: function setInstantMintLimitDuration( 540: function setInstantRedemptionLimitDuration( 554: function setMintFee( 567: function setRedeemFee( 581: function setMinimumDepositAmount( 599: function setMinimumRedemptionAmount( 622: function setMinimumBUIDLRedemptionAmount( 638: function setOracle( 650: function setFeeReceiver( 663: function setInvestorBasedRateLimiter(
link , link, link, link, link, link, link, link, link, link, link, link
</details>File: ./contracts/ousg/rOUSG.sol 613: function setOracle(address _oracle) external onlyRole(DEFAULT_ADMIN_ROLE) { 650: function setKYCRegistry( 656: function setKYCRequirementGroup(
@author
is missing from contract
There are 1 instances of this issue:
<details> <summary>see instances</summary></details>File: ./contracts/ousg/rOUSG.sol 28: /** 29: * @title Interest-bearing ERC20-like token for OUSG. 30: * 31: * rOUSG balances are dynamic and represent the holder's share of the underlying OUSG 32: * controlled by the protocol. To calculate each account's balance, we do 33: * 34: * shares[account] * ousgPrice 35: * 36: * For example, assume that we have: 37: * 38: * ousgPrice = 100.505 39: * sharesOf(user1) -> 100 40: * sharesOf(user2) -> 400 41: * 42: * Therefore: 43: * 44: * balanceOf(user1) -> 105 tokens which corresponds 105 rOUSG 45: * balanceOf(user2) -> 420 tokens which corresponds 420 rOUSG 46: * 47: * Since balances of all token holders change when the price of OUSG changes, this 48: * token cannot fully implement ERC20 standard: it only emits `Transfer` events 49: * upon explicit transfer between holders. In contrast, when total amount of pooled 50: * Cash increases, no `Transfer` events are generated: doing so would require emitting 51: * an event for each token holder and thus running an unbounded loop. 52: * 53: */
Consider defining in only one contract so that values cannot become out of sync when only one location is updated. A cheap way to store constants in a single location is to create an internal constant
in a library
. If the variable is a local cache of another contract's value, consider making the cache variable internal or private, which will require external users to query the contract with the source of truth, so that callers don't get out of sync.
There are 3 instances of this issue:
<details> <summary>see instances</summary></details>File: ./contracts/ousg/rOUSG.sol //@audit The same constant is already defined on file : ./contracts/ousg/ousgInstantManager.sol 87: uint256 public constant OUSG_TO_ROUSG_SHARES_MULTIPLIER = 10_000; //@audit The same constant is already defined on file : ./contracts/ousg/ousgInstantManager.sol 93: bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); //@audit The same constant is already defined on file : ./contracts/ousg/ousgInstantManager.sol 95: bytes32 public constant CONFIGURER_ROLE = keccak256("CONFIGURER_ROLE");
There are 16 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol //@audit `0` 316: if (usdcfees > 0) { //@audit `6` 283: IERC20Metadata(address(usdc)).decimals() == 6, //@audit `0` 311: ousgAmountOut > 0, //@audit `0` 450: if (usdcFees > 0) { //@audit `6` 392: IERC20Metadata(address(usdc)).decimals() == 6, //@audit `6` 396: IERC20Metadata(address(buidl)).decimals() == 6, //@audit `0` 418: usdcAmountOut > 0, //@audit `MINIMUM_OUSG_PRICE` 482: price > MINIMUM_OUSG_PRICE, //@audit `200` 557: require(mintFee < 200, "OUSGInstantManager::setMintFee: Fee too high"); //@audit `200` 570: require(redeemFee < 200, "OUSGInstantManager::setRedeemFee: Fee too high"); //@audit `FEE_GRANULARITY` 585: _minimumDepositAmount >= FEE_GRANULARITY, //@audit `FEE_GRANULARITY` 603: _minimumRedemptionAmount >= FEE_GRANULARITY,
link , link, link, link, link, link, link, link, link, link, link, link
</details>File: ./contracts/ousg/rOUSG.sol //@audit `0` 416: require(_OUSGAmount > 0, "rOUSG: can't wrap zero OUSG tokens"); //@audit `OUSG_TO_ROUSG_SHARES_MULTIPLIER` 434: if (ousgSharesAmount < OUSG_TO_ROUSG_SHARES_MULTIPLIER) //@audit `0` 432: require(_rOUSGAmount > 0, "rOUSG: can't unwrap zero rOUSG tokens"); //@audit `OUSG_TO_ROUSG_SHARES_MULTIPLIER` 629: if (ousgSharesAmount < OUSG_TO_ROUSG_SHARES_MULTIPLIER)
contract
It is recommended that Solidity contracts are fully annotated using NatSpec for all public interfaces (everything in the ABI). It is clearly stated in the Solidity official documentation. In complex projects such as Defi, the interpretation of all functions and their arguments and returns is important for code readability and auditability.source
There are 49 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 57: bytes32 public constant CONFIGURER_ROLE = keccak256("CONFIGURER_ROLE"); 60: bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); 63: uint256 public constant MINIMUM_OUSG_PRICE = 105e18; 66: uint256 public constant FEE_GRANULARITY = 10_000; 69: uint256 public constant OUSG_TO_ROUSG_SHARES_MULTIPLIER = 10_000; 72: IERC20 public immutable usdc; // 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; 75: IRWALike public immutable ousg; 78: ROUSG public immutable rousg; 81: IERC20 public immutable buidl; 84: IBUIDLRedeemer public immutable buidlRedeemer; 87: uint256 public immutable decimalsMultiplier; 90: address public immutable usdcReceiver; 93: IRWAOracle public oracle; 96: address public feeReceiver; 99: uint256 public mintFee = 0; 102: uint256 public redeemFee = 0; 106: uint256 public minimumDepositAmount = 100_000e6; 110: uint256 public minimumRedemptionAmount = 50_000e6; 113: bool public mintPaused; 116: bool public redeemPaused; 120: uint256 public minBUIDLRedeemAmount = 250_000e6; 123: IInvestorBasedRateLimiter public investorBasedRateLimiter; 278: function _mint( 388: function _redeem( 458: function _redeemBUIDL(uint256 buidlAmountToRedeem) internal { 794: function multiexcall(
link , link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link
File: ./contracts/ousg/rOUSG.sol 78: uint256 private totalShares; 81: IRWAOracle public oracle; 84: IERC20 public ousg; 87: uint256 public constant OUSG_TO_ROUSG_SHARES_MULTIPLIER = 10_000; 90: error UnwrapTooSmall(); 94: bytes32 public constant BURNER_ROLE = keccak256("BURN_ROLE"); 95: bytes32 public constant CONFIGURER_ROLE = keccak256("CONFIGURER_ROLE"); 102: function initialize( 112: function __rOUSG_init( 128: function __rOUSG_init_unchained( 378: function getOUSGPrice() public view returns (uint256 price) { 642: function pause() external onlyRole(PAUSER_ROLE) { 646: function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) { 650: function setKYCRegistry( 656: function setKYCRequirementGroup(
link , link, link, link, link, link, link, link, link, link, link, link, link, link, link
File: ./contracts/ousg/rOUSGFactory.sol 38: bytes32 public constant DEFAULT_ADMIN_ROLE = bytes32(0); 40: address internal immutable guardian; 41: ROUSG public rOUSGImplementation; 42: ProxyAdmin public rOUSGProxyAdmin; 43: TokenProxy public rOUSGProxy; 45: bool public initialized = false; 47: constructor(address _guardian) { 149: modifier onlyGuardian() {
link , link, link, link, link, link, link, link
</details>See the control structures section of the Solidity Style Guide
There are 44 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 230: function mint( 254: function mintRebasingOUSG( 278: function _mint( 335: function redeem( 362: function redeemRebasingOUSG( 388: function _redeem( 498: function setInstantMintLimit( 512: function setInstantRedemptionLimit( 526: function setInstantMintLimitDuration( 540: function setInstantRedemptionLimitDuration( 554: function setMintFee( 567: function setRedeemFee( 581: function setMinimumDepositAmount( 599: function setMinimumRedemptionAmount( 622: function setMinimumBUIDLRedemptionAmount( 638: function setOracle( 650: function setFeeReceiver( 663: function setInvestorBasedRateLimiter( 685: function _getMintAmount( 699: function _getRedemptionAmount( 713: function _getInstantMintFees( 725: function _getInstantRedemptionFees( 794: function multiexcall( 819: function retrieveTokens(
link , link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link
File: ./contracts/ousg/rOUSG.sol 102: function initialize( 112: function __rOUSG_init( 128: function __rOUSG_init_unchained( 231: function allowance( 276: function transferFrom( 302: function increaseAllowance( 328: function decreaseAllowance( 363: function getSharesByROUSG( 397: function transferShares( 450: function _transfer( 472: function _approve( 501: function _transferShares( 529: function _mintShares( 554: function _burnShares( 586: function _beforeTokenTransfer( 624: function burn( 650: function setKYCRegistry( 656: function setKYCRequirementGroup(
link , link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link
</details>File: ./contracts/ousg/rOUSGFactory.sol 70: function deployRebasingOUSG( 121: function multiexcall(
Consider adding parameters to the error to indicate which user or values caused the failure
There are 1 instances of this issue:
<details> <summary>see instances</summary></details>File: ./contracts/ousg/rOUSG.sol 90: error UnwrapTooSmall();
In the event of a security breach or any unforeseen emergency, swiftly suspending all protocol operations becomes crucial. Having a mechanism in place to halt all functions collectively, instead of pausing individual contracts separately, substantially enhances the efficiency of mitigating ongoing attacks or vulnerabilities. This not only quickens the response time to potential threats but also reduces operational stress during these critical periods. Therefore, consider integrating a 'circuit breaker' or 'emergency stop' function into the smart contract system architecture. Such a feature would provide the capability to suspend the entire protocol instantly, which could prove invaluable during a time-sensitive crisis management situation.
There are 1 instances of this issue:
<details> <summary>see instances</summary></details>File: ./contracts/ousg/rOUSG.sol //@audit `PausableUpgradeable` 55: contract ROUSG is
When an action is triggered based on a user's action, not being able to filter based on who triggered the action makes event processing a lot more cumbersome. Including the msg.sender
the events of these types of action will make events much more useful to end users, especially when msg.sender
is not tx.origin
.
There are 18 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 558: emit MintFeeSet(mintFee, _mintFee); 571: emit RedeemFeeSet(redeemFee, _redeemFee); 589: emit MinimumDepositAmountSet(minimumDepositAmount, _minimumDepositAmount); 606: emit MinimumRedemptionAmountSet( 607: minimumRedemptionAmount, 608: _minimumRedemptionAmount 609: ); 625: emit MinimumBUIDLRedemptionAmountSet( 626: minBUIDLRedeemAmount, 627: _minimumBUIDLRedemptionAmount 628: ); 641: emit OracleSet(address(oracle), _oracle); 654: emit FeeReceiverSet(feeReceiver, _feeReceiver); 666: emit InvestorBasedRateLimiterSet( 667: address(investorBasedRateLimiter), 668: _investorBasedRateLimiter 669: ); 770: emit MintPaused(); 776: emit MintUnpaused(); 782: emit RedeemPaused(); 788: emit RedeemUnpaused();
link , link, link, link, link, link, link, link, link, link, link, link
File: ./contracts/ousg/rOUSG.sol 457: emit Transfer(_sender, _recipient, _amount); 458: emit TransferShares(_sender, _recipient, _sharesToTransfer); 481: emit Approval(_owner, _spender, _amount); 614: emit OracleSet(address(oracle), _oracle); 639: emit TransferShares(_account, address(0), ousgSharesAmount);
</details>File: ./contracts/ousg/rOUSGFactory.sol 096: emit rOUSGDeployed( 097: address(rOUSGProxy), 098: address(rOUSGProxyAdmin), 099: address(rOUSGImplementation), 100: rOUSGProxied.name(), 101: rOUSGProxied.symbol() 102: );
It is preferable to have all the external and public function in an interface to make using them easier by developers. This helps ensure the whole API is extracted in a interface.
There are 20 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 479: function getOUSGPrice() public view returns (uint256 price) { 480: (price, ) = oracle.getPriceData(); 768: function pauseMint() external onlyRole(PAUSER_ROLE) { 769: mintPaused = true; 774: function unpauseMint() external onlyRole(DEFAULT_ADMIN_ROLE) { 775: mintPaused = false; 780: function pauseRedeem() external onlyRole(PAUSER_ROLE) { 781: redeemPaused = true; 786: function unpauseRedeem() external onlyRole(DEFAULT_ADMIN_ROLE) { 787: redeemPaused = false; 819: function retrieveTokens( 820: address token, 821: address to, 822: uint256 amount 823: ) external onlyRole(DEFAULT_ADMIN_ROLE) {
link , link, link, link, link, link
File: ./contracts/ousg/rOUSG.sol 102: function initialize( 103: address _kycRegistry, 104: uint256 requirementGroup, 105: address _ousg, 106: address guardian, 107: address _oracle 108: ) public virtual initializer { 302: function increaseAllowance( 303: address _spender, 304: uint256 _addedValue 305: ) public returns (bool) { 328: function decreaseAllowance( 329: address _spender, 330: uint256 _subtractedValue 331: ) public returns (bool) { 347: function getTotalShares() public view returns (uint256) { 348: return totalShares; 356: function sharesOf(address _account) public view returns (uint256) { 357: return _sharesOf(_account); 363: function getSharesByROUSG( 364: uint256 _rOUSGAmount 365: ) public view returns (uint256) { 373: function getROUSGByShares(uint256 _shares) public view returns (uint256) { 374: return 378: function getOUSGPrice() public view returns (uint256 price) { 379: (price, ) = oracle.getPriceData(); 397: function transferShares( 398: address _recipient, 399: uint256 _sharesAmount 400: ) public returns (uint256) { 415: function wrap(uint256 _OUSGAmount) external whenNotPaused { 416: require(_OUSGAmount > 0, "rOUSG: can't wrap zero OUSG tokens"); 431: function unwrap(uint256 _rOUSGAmount) external whenNotPaused { 432: require(_rOUSGAmount > 0, "rOUSG: can't unwrap zero rOUSG tokens"); 642: function pause() external onlyRole(PAUSER_ROLE) { 643: _pause(); 646: function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) { 647: _unpause();
link , link, link, link, link, link, link, link, link, link, link, link, link
</details>File: ./contracts/ousg/rOUSGFactory.sol 70: function deployRebasingOUSG( 71: address kycRegistry, 72: uint256 requirementGroup, 73: address ousg, 74: address oracle 75: ) external onlyGuardian returns (address, address, address) {
require()
checks should be refactored to a modifier or functionThe compiler will inline the function, which will avoid JUMP
instructions usually associated with functions
There are 1 instances of this issue:
<details> <summary>see instances</summary></details>File: ./contracts/ousg/ousgInstantManager.sol 808: require(success, "Call Failed");
The contract used interfaces should be imported first, followed by all other files. The examples below do not follow this layout.
There are 2 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 20: import "contracts/external/openzeppelin/contracts/token/IERC20Metadata.sol";
</details>File: ./contracts/ousg/rOUSG.sol 26: import "contracts/rwaOracles/IRWAOracle.sol";
Using import declarations of the form import {<identifier_name>} from "some/ file.sol"
avoids polluting the symbol namespace making flattened files smaller, and speeds up compilation
There are 23 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 18: import "contracts/external/openzeppelin/contracts/access/AccessControlEnumerable.sol"; 19: import "contracts/external/openzeppelin/contracts/security/ReentrancyGuard.sol"; 20: import "contracts/external/openzeppelin/contracts/token/IERC20Metadata.sol"; 21: import "contracts/ousg/rOUSG.sol"; 22: import "contracts/interfaces/IRWALike.sol"; 23: import "contracts/interfaces/IBUIDLRedeemer.sol"; 24: import "contracts/InstantMintTimeBasedRateLimiter.sol"; 25: import "contracts/interfaces/IOUSGInstantManager.sol"; 26: import "contracts/interfaces/IMulticall.sol"; 27: import "contracts/interfaces/IInvestorBasedRateLimiter.sol";
link , link, link, link, link, link, link, link, link, link
File: ./contracts/ousg/rOUSG.sol 18: import "contracts/external/openzeppelin/contracts/token/IERC20.sol"; 19: import "contracts/external/openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; 20: import "contracts/external/openzeppelin/contracts-upgradeable/token/ERC20/IERC20MetadataUpgradeable.sol"; 21: import "contracts/external/openzeppelin/contracts-upgradeable/proxy/Initializable.sol"; 22: import "contracts/external/openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"; 23: import "contracts/external/openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; 24: import "contracts/external/openzeppelin/contracts-upgradeable/access/AccessControlEnumerableUpgradeable.sol"; 25: import "contracts/kyc/KYCRegistryClientUpgradeable.sol"; 26: import "contracts/rwaOracles/IRWAOracle.sol";
link , link, link, link, link, link, link, link, link
</details>File: ./contracts/ousg/rOUSGFactory.sol 19: import "contracts/external/openzeppelin/contracts/proxy/ProxyAdmin.sol"; 20: import "contracts/Proxy.sol"; 21: import "contracts/ousg/rOUSG.sol"; 22: import "contracts/interfaces/IMulticall.sol";
Some lines use // x
and some use //x
. The instances below point out the usages that don't follow the majority, within each file
There are 1 instances of this issue:
<details> <summary>see instances</summary></details>File: ./contracts/ousg/rOUSG.sol 1: //SPDX-License-Identifier: BUSL-1.1
require
/error
Some parts of the codebase use require
statements, while others use custom error
s. Consider refactoring the code to use the same approach: the following findings represent the minority of require
vs error
, and they show the first occurance in each file, for brevity.
There are 2 instances of this issue:
<details> <summary>see instances</summary></details>File: ./contracts/ousg/rOUSG.sol 435: revert UnwrapTooSmall(); 630: revert UnwrapTooSmall();
There are 3 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 278: function _mint( 388: function _redeem(
</details>File: ./contracts/ousg/rOUSGFactory.sol 70: function deployRebasingOUSG(
Usually lines in source code are limited to 80 characters. Today's screens are much larger so it's reasonable to stretch this in some cases. The solidity style guide recommends a maximumum line length of 120 characters, so the lines below should be split when they reach that length.
There are 15 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 6: bb ,bbB, bbbbbb b�bb bbb bbbbb�b�bbbb bbbb bb bbbb�b�b�bbbb bbbbb�b�bbb, 7: bb bb bbb�' bbb bbb bb bbb bbb bbbbb, bb bbb bbbb bbb bbbb 8: bb bbb bb bb bb bb bbb bbb bb bbbb bb bbb bbb jbb bbb 9: bb bb bbb bbb� bbb bb bbb bbb bb bbbbb bbb bbbb� bbb ,bbb� 10: bb "bb, bb�b�bbbbbbbbbbbb bbbbbbbbbb� bb bbb bbbbbbbb�b� bbbbbbbbb�`
File: ./contracts/ousg/rOUSG.sol 6: baabaa ,baabaaB, baabaabaabaabaabaa baabaabaa baabaabaa baabaabaabaabaabaabaabaabaabaa baabaabaabaa baabaa baabaabaabaabaabaabaabaabaabaa baabaabaabaabaabaabaabaabaa, 7: baabaa baabaa baabaabaa' baabaabaa baabaabaa baabaa baabaabaa baabaabaa baabaabaabaabaa, baabaa baabaabaa baabaabaabaa baabaabaa baabaabaabaa 8: baabaa baabaabaa baabaa baabaa baabaa baabaa baabaabaa baabaabaa baabaa baabaabaabaa baabaa baabaabaa baabaabaa jbaabaa baabaabaa 9: baabaa baabaa baabaabaa baabaabaa baabaabaa baabaa baabaabaa baabaabaa baabaa baabaabaabaabaa baabaabaa baabaabaabaa baabaabaa ,baabaabaa 10: baabaa "baabaa, baabaabaabaabaabaabaabaabaabaabaabaabaabaabaa baabaabaabaabaabaabaabaabaabaa baabaa baabaabaa baabaabaabaabaabaabaabaabaa baabaabaabaabaabaabaabaabaa`
</details>File: ./contracts/ousg/rOUSGFactory.sol 6: bb ,bbB, bbbbbb b�bb bbb bbbbb�b�bbbb bbbb bb bbbb�b�b�bbbb bbbbb�b�bbb, 7: bb bb bbb�' bbb bbb bb bbb bbb bbbbb, bb bbb bbbb bbb bbbb 8: bb bbb bb bb bb bb bbb bbb bb bbbb bb bbb bbb jbb bbb 9: bb bb bbb bbb� bbb bb bbb bbb bb bbbbb bbb bbbb� bbb ,bbb� 10: bb "bb, bb�b�bbbbbbbbbbbb bbbbbbbbbb� bb bbb bbbbbbbb�b� bbbbbbbbb�`
Consider using solidity 0.8.18 or later to benefit from multiple things including the named mappings named mappings to make it easier to understand the purpose of each mapping
There are 3 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 16: pragma solidity 0.8.16;
File: ./contracts/ousg/rOUSG.sol 16: pragma solidity 0.8.16;
</details>File: ./contracts/ousg/rOUSGFactory.sol 16: pragma solidity 0.8.16;
Consider adding more detail to these error strings
There are 2 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol //@audit This message need more details : Call Failed 808: require(success, "Call Failed");
</details>File: ./contracts/ousg/rOUSGFactory.sol //@audit This message need more details : Call Failed 129: require(success, "Call Failed");
override
is unnecessaryStarting with Solidity version 0.8.8, using the override
keyword when the function solely overrides an interface function, and the function doesn't exist in multiple base contracts, is unnecessary.
There are 20 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 230: function mint( 231: uint256 usdcAmountIn 232: ) 233: external 234: override 254: function mintRebasingOUSG( 255: uint256 usdcAmountIn 256: ) 257: external 258: override 335: function redeem( 336: uint256 ousgAmountIn 337: ) 338: external 339: override 362: function redeemRebasingOUSG( 363: uint256 rousgAmountIn 364: ) 365: external 366: override 498: function setInstantMintLimit( 499: uint256 _instantMintLimit 500: ) external override onlyRole(CONFIGURER_ROLE) { 512: function setInstantRedemptionLimit( 513: uint256 _instantRedemptionLimit 514: ) external override onlyRole(CONFIGURER_ROLE) { 526: function setInstantMintLimitDuration( 527: uint256 _instantMintLimitDuration 528: ) external override onlyRole(CONFIGURER_ROLE) { 540: function setInstantRedemptionLimitDuration( 541: uint256 _instantRedemptionLimitDuratioin 542: ) external override onlyRole(CONFIGURER_ROLE) { 554: function setMintFee( 555: uint256 _mintFee 556: ) external override onlyRole(CONFIGURER_ROLE) { 567: function setRedeemFee( 568: uint256 _redeemFee 569: ) external override onlyRole(CONFIGURER_ROLE) { 581: function setMinimumDepositAmount( 582: uint256 _minimumDepositAmount 583: ) external override onlyRole(CONFIGURER_ROLE) { 599: function setMinimumRedemptionAmount( 600: uint256 _minimumRedemptionAmount 601: ) external override onlyRole(CONFIGURER_ROLE) { 622: function setMinimumBUIDLRedemptionAmount( 623: uint256 _minimumBUIDLRedemptionAmount 624: ) external override onlyRole(DEFAULT_ADMIN_ROLE) { 638: function setOracle( 639: address _oracle 640: ) external override onlyRole(DEFAULT_ADMIN_ROLE) { 650: function setFeeReceiver( 651: address _feeReceiver 652: ) external override onlyRole(DEFAULT_ADMIN_ROLE) { 663: function setInvestorBasedRateLimiter( 664: address _investorBasedRateLimiter 665: ) external override onlyRole(DEFAULT_ADMIN_ROLE) { 794: function multiexcall( 795: ExCallData[] calldata exCallData 796: ) 797: external 798: payable 799: override
link , link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link
File: ./contracts/ousg/rOUSG.sol 650: function setKYCRegistry( 651: address registry 652: ) external override onlyRole(CONFIGURER_ROLE) { 656: function setKYCRequirementGroup( 657: uint256 group 658: ) external override onlyRole(CONFIGURER_ROLE) {
</details>File: ./contracts/ousg/rOUSGFactory.sol 121: function multiexcall( 122: ExCallData[] calldata exCallData 123: ) external payable override onlyGuardian returns (bytes[] memory results) {
The contracts below already extend the specified contract, so there is no need to list it in the inheritance list again
There are 3 instances of this issue:
<details> <summary>see instances</summary></details>File: ./contracts/ousg/rOUSG.sol //@audit `Initializable` is already inherited by `ContextUpgradeable` 55: contract ROUSG is 56: Initializable, 57: ContextUpgradeable, //@audit `ContextUpgradeable` is already inherited by `PausableUpgradeable` 55: contract ROUSG is 56: Initializable, 57: ContextUpgradeable, 58: PausableUpgradeable, //@audit `IERC20Upgradeable` is already inherited by `IERC20MetadataUpgradeable` 55: contract ROUSG is 56: Initializable, 57: ContextUpgradeable, 58: PausableUpgradeable, 59: AccessControlEnumerableUpgradeable, 60: KYCRegistryClientUpgradeable, 61: IERC20Upgradeable, 62: IERC20MetadataUpgradeable
This especially problematic when the setter also emits the same value, which may be confusing to offline parsers
There are 6 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol //@audit `mintFee` and `_mintFee` are never checked for the same value setting 554: function setMintFee( 555: uint256 _mintFee 556: ) external override onlyRole(CONFIGURER_ROLE) { 557: require(mintFee < 200, "OUSGInstantManager::setMintFee: Fee too high"); 558: emit MintFeeSet(mintFee, _mintFee); 559: mintFee = _mintFee; //@audit `redeemFee` and `_redeemFee` are never checked for the same value setting 567: function setRedeemFee( 568: uint256 _redeemFee 569: ) external override onlyRole(CONFIGURER_ROLE) { 570: require(redeemFee < 200, "OUSGInstantManager::setRedeemFee: Fee too high"); 571: emit RedeemFeeSet(redeemFee, _redeemFee); 572: redeemFee = _redeemFee; //@audit `minimumDepositAmount` and `_minimumDepositAmount` are never checked for the same value setting 581: function setMinimumDepositAmount( 582: uint256 _minimumDepositAmount 583: ) external override onlyRole(CONFIGURER_ROLE) { 584: require( 585: _minimumDepositAmount >= FEE_GRANULARITY, 586: "setMinimumDepositAmount: Amount too small" 587: ); 588: 589: emit MinimumDepositAmountSet(minimumDepositAmount, _minimumDepositAmount); 590: minimumDepositAmount = _minimumDepositAmount; //@audit `minimumRedemptionAmount` and `_minimumRedemptionAmount` are never checked for the same value setting 599: function setMinimumRedemptionAmount( 600: uint256 _minimumRedemptionAmount 601: ) external override onlyRole(CONFIGURER_ROLE) { 602: require( 603: _minimumRedemptionAmount >= FEE_GRANULARITY, 604: "setMinimumRedemptionAmount: Amount too small" 605: ); 606: emit MinimumRedemptionAmountSet( 607: minimumRedemptionAmount, 608: _minimumRedemptionAmount 609: ); 610: minimumRedemptionAmount = _minimumRedemptionAmount; //@audit `minBUIDLRedeemAmount` and `_minimumBUIDLRedemptionAmount` are never checked for the same value setting 622: function setMinimumBUIDLRedemptionAmount( 623: uint256 _minimumBUIDLRedemptionAmount 624: ) external override onlyRole(DEFAULT_ADMIN_ROLE) { 625: emit MinimumBUIDLRedemptionAmountSet( 626: minBUIDLRedeemAmount, 627: _minimumBUIDLRedemptionAmount 628: ); 629: minBUIDLRedeemAmount = _minimumBUIDLRedemptionAmount; //@audit `feeReceiver` and `_feeReceiver` are never checked for the same value setting 650: function setFeeReceiver( 651: address _feeReceiver 652: ) external override onlyRole(DEFAULT_ADMIN_ROLE) { 653: require(_feeReceiver != address(0), "FeeReceiver cannot be 0x0"); 654: emit FeeReceiverSet(feeReceiver, _feeReceiver); 655: feeReceiver = _feeReceiver;
link , link, link, link, link, link
</details>SafeTransferLib.safeTransferETH()
or Address.sendValue()
for clearer semantic meaningThese Functions indicate their purpose with their name more clearly than using low-level calls.
There are 2 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 805: (bool success, bytes memory ret) = address(exCallData[i].target).call{
</details>File: ./contracts/ousg/rOUSGFactory.sol 126: (bool success, bytes memory ret) = address(exCallData[i].target).call{
1e6
) rather than decimal literals (e.g. 1000000
), for readabilityWhile the compiler knows to optimize away the exponentiation, it's still better coding practice to use idioms that do not require compiler optimization, if they exist
There are 3 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol //@audit `10_000` 66: uint256 public constant FEE_GRANULARITY = 10_000; //@audit `10_000` 69: uint256 public constant OUSG_TO_ROUSG_SHARES_MULTIPLIER = 10_000;
</details>File: ./contracts/ousg/rOUSG.sol //@audit `10_000` 87: uint256 public constant OUSG_TO_ROUSG_SHARES_MULTIPLIER = 10_000;
msg.sender
checks to a common authorization modifier
There are 3 instances of this issue:
<details> <summary>see instances</summary></details>File: ./contracts/ousg/ousgInstantManager.sol 299: usdc.allowance(msg.sender, address(this)) >= usdcAmountIn, 345: ousg.allowance(msg.sender, address(this)) >= ousgAmountIn, 372: rousg.allowance(msg.sender, address(this)) >= rousgAmountIn,
Natspec
commentsConsider adding some Natspec
comments on critical state variables to explain what they are supposed to do: this will help for future code reviews.
There are 34 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol //@audit CONFIGURER_ROLE need comments 57: bytes32 public constant CONFIGURER_ROLE = keccak256("CONFIGURER_ROLE"); //@audit PAUSER_ROLE need comments 60: bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); //@audit MINIMUM_OUSG_PRICE need comments 63: uint256 public constant MINIMUM_OUSG_PRICE = 105e18; //@audit FEE_GRANULARITY need comments 66: uint256 public constant FEE_GRANULARITY = 10_000; //@audit OUSG_TO_ROUSG_SHARES_MULTIPLIER need comments 69: uint256 public constant OUSG_TO_ROUSG_SHARES_MULTIPLIER = 10_000; //@audit usdc need comments 72: IERC20 public immutable usdc; // 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; //@audit ousg need comments 75: IRWALike public immutable ousg; //@audit rousg need comments 78: ROUSG public immutable rousg; //@audit buidl need comments 81: IERC20 public immutable buidl; //@audit buidlRedeemer need comments 84: IBUIDLRedeemer public immutable buidlRedeemer; //@audit decimalsMultiplier need comments 87: uint256 public immutable decimalsMultiplier; //@audit usdcReceiver need comments 90: address public immutable usdcReceiver; //@audit oracle need comments 93: IRWAOracle public oracle; //@audit feeReceiver need comments 96: address public feeReceiver; //@audit mintFee need comments 99: uint256 public mintFee = 0; //@audit redeemFee need comments 102: uint256 public redeemFee = 0; //@audit minimumDepositAmount need comments 106: uint256 public minimumDepositAmount = 100_000e6; //@audit minimumRedemptionAmount need comments 110: uint256 public minimumRedemptionAmount = 50_000e6; //@audit mintPaused need comments 113: bool public mintPaused; //@audit redeemPaused need comments 116: bool public redeemPaused; //@audit minBUIDLRedeemAmount need comments 120: uint256 public minBUIDLRedeemAmount = 250_000e6; //@audit investorBasedRateLimiter need comments 123: IInvestorBasedRateLimiter public investorBasedRateLimiter;
link , link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link
File: ./contracts/ousg/rOUSG.sol //@audit totalShares need comments 78: uint256 private totalShares; //@audit oracle need comments 81: IRWAOracle public oracle; //@audit ousg need comments 84: IERC20 public ousg; //@audit OUSG_TO_ROUSG_SHARES_MULTIPLIER need comments 87: uint256 public constant OUSG_TO_ROUSG_SHARES_MULTIPLIER = 10_000; //@audit BURNER_ROLE need comments 94: bytes32 public constant BURNER_ROLE = keccak256("BURN_ROLE"); //@audit CONFIGURER_ROLE need comments 95: bytes32 public constant CONFIGURER_ROLE = keccak256("CONFIGURER_ROLE");
link , link, link, link, link, link
File: ./contracts/ousg/rOUSGFactory.sol //@audit DEFAULT_ADMIN_ROLE need comments 38: bytes32 public constant DEFAULT_ADMIN_ROLE = bytes32(0); //@audit guardian need comments 40: address internal immutable guardian; //@audit rOUSGImplementation need comments 41: ROUSG public rOUSGImplementation; //@audit rOUSGProxyAdmin need comments 42: ProxyAdmin public rOUSGProxyAdmin; //@audit rOUSGProxy need comments 43: TokenProxy public rOUSGProxy; //@audit initialized need comments 45: bool public initialized = false;
link , link, link, link, link, link
</details>While 100% code coverage does not guarantee that there are no bugs, it often will catch easy-to-find bugs, and will ensure that there are fewer regressions when the code invariably has to be modified. Furthermore, in order to get full coverage, code authors will often have to re-organize their code so that it is more modular, so that each component can be tested separately, which reduces interdependencies between modules and layers, and makes for code that is easier to reason about and audit.
There are 1 instances of this issue:
<details> <summary>see instances</summary></details>File: ./contracts/ousg/ousgInstantManager.sol @audit Multiple files 1:
There are 2 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 16: pragma solidity 0.8.16; 17: 18: import "contracts/external/openzeppelin/contracts/access/AccessControlEnumerable.sol";
</details>File: ./contracts/ousg/rOUSG.sol 16: pragma solidity 0.8.16; 17: 18: import "contracts/external/openzeppelin/contracts/token/IERC20.sol";
Critical functions in Solidity contracts should follow a two-step procedure to enhance security, minimize human error, and ensure proper access control. By dividing sensitive operations into distinct phases, such as initiation and confirmation, developers can introduce a safeguard against unintended actions or unauthorized access.
There are 15 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 498: function setInstantMintLimit( 512: function setInstantRedemptionLimit( 526: function setInstantMintLimitDuration( 540: function setInstantRedemptionLimitDuration( 554: function setMintFee( 567: function setRedeemFee( 581: function setMinimumDepositAmount( 599: function setMinimumRedemptionAmount( 622: function setMinimumBUIDLRedemptionAmount( 638: function setOracle( 650: function setFeeReceiver( 663: function setInvestorBasedRateLimiter(
link , link, link, link, link, link, link, link, link, link, link, link
</details>File: ./contracts/ousg/rOUSG.sol 613: function setOracle(address _oracle) external onlyRole(DEFAULT_ADMIN_ROLE) { 650: function setKYCRegistry( 656: function setKYCRequirementGroup(
Modifiers in Solidity can improve code readability and modularity by encapsulating repetitive checks, such as address validity checks, into a reusable construct. For example, an onlyOwner
modifier can be used to replace repetitive require(msg.sender == owner)
checks across several functions, reducing code redundancy and enhancing maintainability. To implement, define a modifier with the check, then apply the modifier to relevant functions.
There are 2 instances of this issue:
<details> <summary>see instances</summary></details>File: ./contracts/ousg/ousgInstantManager.sol //@audit `specifed` is misspelled 219: /** 220: * @notice Calculates fees and triggers minting OUSG for a given amount of USDC 221: * 222: * @dev Please note that the fees are accumulated in `feeReceiver` 223: * 224: * @param usdcAmountIn amount of USDC exchanged for OUSG (in whatever decimals 225: * specifed by usdc token contract) 226: * 227: * @return ousgAmountOut The quantity of OUSG minted for the user 228: * (18 decimals per OUSG contract) 229: */ //@audit `specifed` is misspelled 243: /** 244: * @notice Calculates fees and triggers minting rOUSG for a given amount of USDC 245: * 246: * @dev Please note that the fees are accumulated in `feeReceiver` 247: * 248: * @param usdcAmountIn amount of USDC exchanged for rOUSG (in whatever decimals 249: * specifed by usdc token contract) 250: * 251: * @return rousgAmountOut The quantity of rOUSG minted for the user 252: * (18 decimals per rOUSG contract) 253: */
Some files/Items are imported but never used
There are 2 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol //@audit `rOUSG` is not used 21: import "contracts/ousg/rOUSG.sol";
</details>File: ./contracts/ousg/rOUSGFactory.sol //@audit `rOUSG` is not used 21: import "contracts/ousg/rOUSG.sol";
At the begining of a project, there is always the need to modify of add something to the source code especialy if any vulnerability is discovered. Therefore, having such system is crusial at least at the first stages of the project
There are 1 instances of this issue:
<details> <summary>see instances</summary></details>File: ./contracts/ousg/ousgInstantManager.sol 49: contract OUSGInstantManager is
When deploying contracts, you should use the latest released version of Solidity.Apart from exceptional cases, only the latest version receives security fixes.
https://docs.soliditylang.org/en/v0.8.20/
There are 3 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 16: pragma solidity 0.8.16;
File: ./contracts/ousg/rOUSG.sol 16: pragma solidity 0.8.16;
</details>File: ./contracts/ousg/rOUSGFactory.sol 16: pragma solidity 0.8.16;
Consider grouping all the system constants under a single file. This finding shows only the first constant for each file.
There are 3 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 57: bytes32 public constant CONFIGURER_ROLE = keccak256("CONFIGURER_ROLE");
File: ./contracts/ousg/rOUSG.sol 87: uint256 public constant OUSG_TO_ROUSG_SHARES_MULTIPLIER = 10_000;
</details>File: ./contracts/ousg/rOUSGFactory.sol 38: bytes32 public constant DEFAULT_ADMIN_ROLE = bytes32(0);
The SMTChecker is a valuable tool for Solidity developers as it helps detect potential vulnerabilities and logical errors in the contract's code. By utilizing Satisfiability Modulo Theories (SMT) solvers, it can reason about the potential states a contract can be in, and therefore, identify conditions that could lead to undesirable behavior. This automatic formal verification can catch issues that might otherwise be missed in manual code reviews or standard testing, enhancing the overall contract's security and reliability.
There are 3 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 16: pragma solidity 0.8.16;
File: ./contracts/ousg/rOUSG.sol 16: pragma solidity 0.8.16;
</details>File: ./contracts/ousg/rOUSGFactory.sol 16: pragma solidity 0.8.16;
See the Whitespace in Expressions section of the Solidity Style Guide
There are 12 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol //@audit remove the whiteSpace before the ',' char 4: bbbb�b ,bbbb, 'b�bbb 5: bbb� bbbb�b�bbb�b�bbb bbbB5 ,, ,, , ,,, ,,, //@audit remove the whiteSpace before the ',' char 5: bbb� bbbb�b�bbb�b�bbb bbbB5 ,, ,, , ,,, ,,, 6: bb ,bbB, bbbbbb b�bb bbb bbbbb�b�bbbb bbbb bb bbbb�b�b�bbbb bbbbb�b�bbb, //@audit remove the whiteSpace before the ',' char 6: bb ,bbB, bbbbbb b�bb bbb bbbbb�b�bbbb bbbb bb bbbb�b�b�bbbb bbbbb�b�bbb, 7: bb bb bbb�' bbb bbb bb bbb bbb bbbbb, bb bbb bbbb bbb bbbb //@audit remove the whiteSpace before the ')' char 480: (price, ) = oracle.getPriceData(); 481: require(
File: ./contracts/ousg/rOUSG.sol //@audit remove the whiteSpace before the ',' char 4: baabaabaabaabaa ,baabaabaabaa, 'baabaabaabaa 5: baabaabaa baabaabaabaabaabaabaabaabaabaabaabaa baabaabaaB5 ,, ,, , ,,, ,,, //@audit remove the whiteSpace before the ',' char 5: baabaabaa baabaabaabaabaabaabaabaabaabaabaabaa baabaabaaB5 ,, ,, , ,,, ,,, 6: baabaa ,baabaaB, baabaabaabaabaabaa baabaabaa baabaabaa baabaabaabaabaabaabaabaabaabaa baabaabaabaa baabaa baabaabaabaabaabaabaabaabaabaa baabaabaabaabaabaabaabaabaa, //@audit remove the whiteSpace before the ',' char 6: baabaa ,baabaaB, baabaabaabaabaabaa baabaabaa baabaabaa baabaabaabaabaabaabaabaabaabaa baabaabaabaa baabaa baabaabaabaabaabaabaabaabaabaa baabaabaabaabaabaabaabaabaa, 7: baabaa baabaa baabaabaa' baabaabaa baabaabaa baabaa baabaabaa baabaabaa baabaabaabaabaa, baabaa baabaabaa baabaabaabaa baabaabaa baabaabaabaa //@audit remove the whiteSpace before the ',' char 09: baabaa baabaa baabaabaa baabaabaa baabaabaa baabaa baabaabaa baabaabaa baabaa baabaabaabaabaa baabaabaa baabaabaabaa baabaabaa ,baabaabaa 10: baabaa "baabaa, baabaabaabaabaabaabaabaabaabaabaabaabaabaabaa baabaabaabaabaabaabaabaabaabaa baabaa baabaabaa baabaabaabaabaabaabaabaabaa baabaabaabaabaabaabaabaabaa` //@audit remove the whiteSpace before the ')' char 379: (price, ) = oracle.getPriceData(); 380: }
</details>File: ./contracts/ousg/rOUSGFactory.sol //@audit remove the whiteSpace before the ',' char 4: bbbb�b ,bbbb, 'b�bbb 5: bbb� bbbb�b�bbb�b�bbb bbbB5 ,, ,, , ,,, ,,, //@audit remove the whiteSpace before the ',' char 5: bbb� bbbb�b�bbb�b�bbb bbbB5 ,, ,, , ,,, ,,, 6: bb ,bbB, bbbbbb b�bb bbb bbbbb�b�bbbb bbbb bb bbbb�b�b�bbbb bbbbb�b�bbb, //@audit remove the whiteSpace before the ',' char 6: bb ,bbB, bbbbbb b�bb bbb bbbbb�b�bbbb bbbb bb bbbb�b�b�bbbb bbbbb�b�bbb, 7: bb bb bbb�' bbb bbb bb bbb bbb bbbbb, bb bbb bbbb bbb bbbb
The functions below take in an unbounded array, and make function calls for entries in the array. While the function will revert if it eventually runs out of gas, it may be a nicer user experience to require()
that the length of the array is below some reasonable maximum, so that the user doesn't have to use up a full transaction's gas only to see that the transaction reverts.
There are 2 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol //@audit array name exCallData 794: function multiexcall( 795: ExCallData[] calldata exCallData 796: ) 797: external 798: payable 799: override 800: onlyRole(DEFAULT_ADMIN_ROLE) 801: returns (bytes[] memory results) 802: { 803: results = new bytes[](exCallData.length); 804: for (uint256 i = 0; i < exCallData.length; ++i) { 805: (bool success, bytes memory ret) = address(exCallData[i].target).call{ 806: value: exCallData[i].value 807: }(exCallData[i].data); 808: require(success, "Call Failed"); 809: results[i] = ret; 810: } 811: }
</details>File: ./contracts/ousg/rOUSGFactory.sol //@audit array name exCallData 121: function multiexcall( 122: ExCallData[] calldata exCallData 123: ) external payable override onlyGuardian returns (bytes[] memory results) { 124: results = new bytes[](exCallData.length); 125: for (uint256 i = 0; i < exCallData.length; ++i) { 126: (bool success, bytes memory ret) = address(exCallData[i].target).call{ 127: value: exCallData[i].value 128: }(exCallData[i].data); 129: require(success, "Call Failed"); 130: results[i] = ret; 131: } 132: }
@dev
is missing from contract
There are 55 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 278: function _mint( 388: function _redeem( 458: function _redeemBUIDL(uint256 buidlAmountToRedeem) internal { 490: /** 491: * @notice Set the mintLimit constraint inside the InstantMintTimeBasedRateLimiter 492: * base contract 493: * 494: * @param _instantMintLimit New limit that dicates how much USDC can be transfered 495: * for minting in a specified duration 496: * (in 6 decimals per the USDC contract) 497: */ 504: /** 505: * @notice Set the redeemLimit constraint inside the InstantMintTimeBasedRateLimiter 506: * base contract 507: * 508: * @param _instantRedemptionLimit New limit that dicates how much USDC 509: * can be redeemed in a specified duration 510: * (in 6 decimals per the USDC contract) 511: */ 518: /** 519: * @notice Sets mintLimitDuration constraint inside the InstantMintTimeBasedRateLimiter 520: * base contract 521: * 522: * @param _instantMintLimitDuration New limit that specifies the interval 523: * (in seconds) in which only `mintLimit` USDC 524: * can be used for minting within 525: */ 532: /** 533: * @notice Sets redeemLimitDuration inside the InstantMintTimeBasedRateLimiter 534: * base contract 535: * 536: * @param _instantRedemptionLimitDuratioin New limit that specifies the interval 537: * (in seconds) in which only `redeemLimit` USDC 538: * can be redeemed within 539: */ 549: /** 550: * @notice Sets the mint fee 551: * 552: * @param _mintFee new mint fee specified in basis points 553: */ 562: /** 563: * @notice Sets the redeem fee. 564: * 565: * @param _redeemFee new redeem fee specified in basis points 566: */ 575: /** 576: * @notice Admin function to set the minimum amount required for a deposit 577: * 578: * @param _minimumDepositAmount The minimum amount required to submit a deposit 579: * request 580: */ 593: /** 594: * @notice Admin function to set the minimum amount to redeem 595: * 596: * @param _minimumRedemptionAmount The minimum amount required to submit a 597: * redemption request 598: */ 617: /** 618: * @notice Admin function to set the minimum amount required to redeem BUIDL 619: * 620: * @param _minimumBUIDLRedemptionAmount The minimum amount required to redeem BUIDL 621: */ 632: /** 633: * @notice Admin function to set the oracle address 634: * 635: * @param _oracle The address of the oracle that provides the OUSG price 636: * in USDC 637: */ 645: /** 646: * @notice Admin function to set the fee receiver address 647: 648: * @param _feeReceiver The address to receive the mint and redemption fees 649: */ 658: /** 659: * @notice Admin function to set the optional investor-based rate limiter 660: * 661: * @param _investorBasedRateLimiter The address of the investor-based rate limiter contract 662: */ 679: /** 680: * @notice Given a deposit amount and a price, returns the OUSG amount due 681: * 682: * @param usdcAmountIn The amount deposited in units of USDC 683: * @param price The price at which to mint 684: */ 693: /** 694: * @notice Given a redemption amount and a price, returns the USDC amount due 695: * 696: * @param ousgAmountBurned The amount of OUSG burned for a redemption 697: * @param price The price at which to redeem 698: */ 707: /** 708: * @notice Given amount of USDC, returns how much in fees are owed 709: * 710: * @param usdcAmount Amount of USDC to calculate fees 711: * (in 6 decimals) 712: */ 719: /** 720: * @notice Given amount of USDC, returns how much in fees are owed 721: * 722: * @param usdcAmount Amount USDC to calculate fees 723: * (in decimals of USDC) 724: */ 755: /// @notice Ensure that the mint functionality is not paused 761: /// @notice Ensure that the redeem functionality is not paused 767: /// @notice Pause the mint functionality 773: /// @notice Unpause the mint functionality 779: /// @notice Pause the redeem functionality 785: /// @notice Unpause the redeem functionality 794: function multiexcall( 813: /** 814: * @notice Rescue and transfer tokens locked in this contract 815: * @param token The address of the token 816: * @param to The address of the recipient 817: * @param amount The amount of token to transfer 818: */
link , link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link
File: ./contracts/ousg/rOUSG.sol 28: /** 29: * @title Interest-bearing ERC20-like token for OUSG. 30: * 31: * rOUSG balances are dynamic and represent the holder's share of the underlying OUSG 32: * controlled by the protocol. To calculate each account's balance, we do 33: * 34: * shares[account] * ousgPrice 35: * 36: * For example, assume that we have: 37: * 38: * ousgPrice = 100.505 39: * sharesOf(user1) -> 100 40: * sharesOf(user2) -> 400 41: * 42: * Therefore: 43: * 44: * balanceOf(user1) -> 105 tokens which corresponds 105 rOUSG 45: * balanceOf(user2) -> 420 tokens which corresponds 420 rOUSG 46: * 47: * Since balances of all token holders change when the price of OUSG changes, this 48: * token cannot fully implement ERC20 standard: it only emits `Transfer` events 49: * upon explicit transfer between holders. In contrast, when total amount of pooled 50: * Cash increases, no `Transfer` events are generated: doing so would require emitting 51: * an event for each token holder and thus running an unbounded loop. 52: * 53: */ 90: error UnwrapTooSmall(); 97: /// @custom:oz-upgrades-unsafe-allow constructor 102: function initialize( 112: function __rOUSG_init( 128: function __rOUSG_init_unchained( 155: /** 156: * @notice Emitted when the oracle address is set 157: * 158: * @param oldOracle The address of the old oracle 159: * @param newOracle The address of the new oracle 160: */ 163: /** 164: * @return the name of the token. 165: */ 170: /** 171: * @return the symbol of the token, usually a shorter version of the 172: * name. 173: */ 178: /** 179: * @return the number of decimals for getting user representation of a token amount. 180: */ 185: /** 186: * @return the amount of tokens in existence. 187: */ 289: /** 290: * @notice Atomically increases the allowance granted to `_spender` by the caller by `_addedValue`. 291: * 292: * This is an alternative to `approve` that can be used as a mitigation for 293: * problems described in: 294: * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol#L42 295: * Emits an `Approval` event indicating the updated allowance. 296: * 297: * Requirements: 298: * 299: * - `_spender` cannot be the the zero address. 300: * - the contract must not be paused. 301: */ 314: /** 315: * @notice Atomically decreases the allowance granted to `_spender` by the caller by `_subtractedValue`. 316: * 317: * This is an alternative to `approve` that can be used as a mitigation for 318: * problems described in: 319: * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol#L42 320: * Emits an `Approval` event indicating the updated allowance. 321: * 322: * Requirements: 323: * 324: * - `_spender` cannot be the zero address. 325: * - `_spender` must have allowance for the caller of at least `_subtractedValue`. 326: * - the contract must not be paused. 327: */ 360: /** 361: * @return the amount of shares that corresponds to `_rOUSGAmount` of rOUSG 362: */ 370: /** 371: * @return the amount of rOUSG that corresponds to `_shares` of OUSG. 372: */ 378: function getOUSGPrice() public view returns (uint256 price) { 445: /** 446: * @notice Moves `_amount` tokens from `_sender` to `_recipient`. 447: * Emits a `Transfer` event. 448: * Emits a `TransferShares` event. 449: */ 461: /** 462: * @notice Sets `_amount` as the allowance of `_spender` over the `_owner` s tokens. 463: * 464: * Emits an `Approval` event. 465: * 466: * Requirements: 467: * 468: * - `_owner` cannot be the zero address. 469: * - `_spender` cannot be the zero address. 470: * - the contract must not be paused. 471: */ 484: /** 485: * @return the amount of shares owned by `_account`. 486: */ 491: /** 492: * @notice Moves `_sharesAmount` shares from `_sender` to `_recipient`. 493: * 494: * Requirements: 495: * 496: * - `_sender` cannot be the zero address. 497: * - `_recipient` cannot be the zero address. 498: * - `_sender` must hold at least `_sharesAmount` shares. 499: * - the contract must not be paused. 500: */ 521: /** 522: * @notice Creates `_sharesAmount` shares and assigns them to `_recipient`, increasing the total amount of shares. 523: * 524: * Requirements: 525: * 526: * - `_recipient` cannot be the zero address. 527: * - the contract must not be paused. 528: */ 642: function pause() external onlyRole(PAUSER_ROLE) { 646: function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) { 650: function setKYCRegistry( 656: function setKYCRequirementGroup(
link , link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link
</details>File: ./contracts/ousg/rOUSGFactory.sol 24: /** 25: * @title ROUSGFactory 26: * @author Ondo Finance 27: * @notice This contract serves as a Factory for the upgradable rOUSG token contract. 28: * Upon calling `deployRebasingOUSG` the `guardian` address (set in constructor) will 29: * deploy the following: 30: * 1) rOUSG - The implementation contract, ERC20 contract with the initializer disabled 31: * 2) ProxyAdmin - OZ ProxyAdmin contract, used to upgrade the proxy instance. 32: * @notice Owner is set to `guardian` address. 33: * 3) TransparentUpgradeableProxy - OZ, proxy contract. Admin is set to `address(proxyAdmin)`. 34: * `_logic' is set to `address(rOUSG)`. 35: * @notice `guardian` address in constructor is a msig. 36: */ 47: constructor(address _guardian) { 149: modifier onlyGuardian() {
function foo(address x, address)
-> function foo(address x, address /* y */)
There are 5 instances of this issue:
<details> <summary>see instances</summary></details>File: ./contracts/ousg/ousgInstantManager.sol //@audit parameters 1, need comment 335: function redeem( 336: uint256 ousgAmountIn //@audit parameters 1, need comment 362: function redeemRebasingOUSG( 363: uint256 rousgAmountIn //@audit parameters 1, need comment 581: function setMinimumDepositAmount( 582: uint256 _minimumDepositAmount //@audit parameters 1, need comment 599: function setMinimumRedemptionAmount( 600: uint256 _minimumRedemptionAmount //@audit parameters 1, need comment 650: function setFeeReceiver( 651: address _feeReceiver
interface
The contract
s should expose an interface
so that other projects can more easily integrate with it, without having to develop their own non-standard variants.
There are 19 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 479: function getOUSGPrice() public view returns (uint256 price) { 768: function pauseMint() external onlyRole(PAUSER_ROLE) { 774: function unpauseMint() external onlyRole(DEFAULT_ADMIN_ROLE) { 780: function pauseRedeem() external onlyRole(PAUSER_ROLE) { 786: function unpauseRedeem() external onlyRole(DEFAULT_ADMIN_ROLE) { 819: function retrieveTokens(
link , link, link, link, link, link
File: ./contracts/ousg/rOUSG.sol 302: function increaseAllowance( 328: function decreaseAllowance( 347: function getTotalShares() public view returns (uint256) { 356: function sharesOf(address _account) public view returns (uint256) { 363: function getSharesByROUSG( 373: function getROUSGByShares(uint256 _shares) public view returns (uint256) { 378: function getOUSGPrice() public view returns (uint256 price) { 397: function transferShares( 415: function wrap(uint256 _OUSGAmount) external whenNotPaused { 431: function unwrap(uint256 _rOUSGAmount) external whenNotPaused { 642: function pause() external onlyRole(PAUSER_ROLE) { 646: function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) {
link , link, link, link, link, link, link, link, link, link, link, link
</details>File: ./contracts/ousg/rOUSGFactory.sol 70: function deployRebasingOUSG(
There are 13 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol //@audit `ReentrancyGuard` 50: ReentrancyGuard, //@audit `InstantMintTimeBasedRateLimiter` 51: InstantMintTimeBasedRateLimiter, //@audit `AccessControlEnumerable` 52: AccessControlEnumerable, //@audit `IOUSGInstantManager` 53: IOUSGInstantManager, //@audit `IMulticall` 54: IMulticall
File: ./contracts/ousg/rOUSG.sol //@audit `Initializable` 56: Initializable, //@audit `ContextUpgradeable` 57: ContextUpgradeable, //@audit `PausableUpgradeable` 58: PausableUpgradeable, //@audit `AccessControlEnumerableUpgradeable` 59: AccessControlEnumerableUpgradeable, //@audit `KYCRegistryClientUpgradeable` 60: KYCRegistryClientUpgradeable, //@audit `IERC20Upgradeable` 61: IERC20Upgradeable, //@audit `IERC20MetadataUpgradeable` 62: IERC20MetadataUpgradeable
link , link, link, link, link, link, link
</details>File: ./contracts/ousg/rOUSGFactory.sol //@audit `IMulticall` 37: contract ROUSGFactory is IMulticall {
@notice
is missing from contract
There are 30 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 278: function _mint( 388: function _redeem( 458: function _redeemBUIDL(uint256 buidlAmountToRedeem) internal { 794: function multiexcall(
File: ./contracts/ousg/rOUSG.sol 28: /** 29: * @title Interest-bearing ERC20-like token for OUSG. 30: * 31: * rOUSG balances are dynamic and represent the holder's share of the underlying OUSG 32: * controlled by the protocol. To calculate each account's balance, we do 33: * 34: * shares[account] * ousgPrice 35: * 36: * For example, assume that we have: 37: * 38: * ousgPrice = 100.505 39: * sharesOf(user1) -> 100 40: * sharesOf(user2) -> 400 41: * 42: * Therefore: 43: * 44: * balanceOf(user1) -> 105 tokens which corresponds 105 rOUSG 45: * balanceOf(user2) -> 420 tokens which corresponds 420 rOUSG 46: * 47: * Since balances of all token holders change when the price of OUSG changes, this 48: * token cannot fully implement ERC20 standard: it only emits `Transfer` events 49: * upon explicit transfer between holders. In contrast, when total amount of pooled 50: * Cash increases, no `Transfer` events are generated: doing so would require emitting 51: * an event for each token holder and thus running an unbounded loop. 52: * 53: */ 90: error UnwrapTooSmall(); 97: /// @custom:oz-upgrades-unsafe-allow constructor 102: function initialize( 112: function __rOUSG_init( 128: function __rOUSG_init_unchained( 163: /** 164: * @return the name of the token. 165: */ 170: /** 171: * @return the symbol of the token, usually a shorter version of the 172: * name. 173: */ 178: /** 179: * @return the number of decimals for getting user representation of a token amount. 180: */ 185: /** 186: * @return the amount of tokens in existence. 187: */ 193: /** 194: * @return the amount of tokens owned by the `_account`. 195: * 196: * @dev Balances are dynamic and equal the `_account`'s OUSG shares multiplied 197: * by the price of OUSG 198: */ 225: /** 226: * @return the remaining number of tokens that `_spender` is allowed to spend 227: * on behalf of `_owner` through `transferFrom`. This is zero by default. 228: * 229: * @dev This value changes when `approve` or `transferFrom` is called. 230: */ 341: /** 342: * @return the total amount of shares in existence. 343: * 344: * @dev The sum of all accounts' shares can be an arbitrary number, therefore 345: * it is necessary to store it in order to calculate each account's relative share. 346: */ 351: /** 352: * @return the amount of shares owned by `_account`. 353: * 354: * @dev This is the equivalent to the amount of OUSG wrapped by `_account`. 355: */ 360: /** 361: * @return the amount of shares that corresponds to `_rOUSGAmount` of rOUSG 362: */ 370: /** 371: * @return the amount of rOUSG that corresponds to `_shares` of OUSG. 372: */ 378: function getOUSGPrice() public view returns (uint256 price) { 484: /** 485: * @return the amount of shares owned by `_account`. 486: */ 572: /** 573: * @dev Hook that is called before any transfer of tokens. This includes 574: * minting and burning. 575: * 576: * Calling conditions: 577: * 578: * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens 579: * will be transferred to `to`. 580: * - when `from` is zero, `amount` tokens will be minted for `to`. 581: * - when `to` is zero, `amount` of ``from``'s tokens will be burned. 582: * - `from` and `to` are never both zero. 583: * 584: * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. 585: */ 642: function pause() external onlyRole(PAUSER_ROLE) { 646: function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) { 650: function setKYCRegistry( 656: function setKYCRequirementGroup(
link , link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link
</details>File: ./contracts/ousg/rOUSGFactory.sol 47: constructor(address _guardian) { 134: /** 135: * @dev Event emitted when upgradable rOUSG is deployed 136: * 137: * @param proxy The address for the proxy contract 138: * @param proxyAdmin The address for the proxy admin contract 139: * @param implementation The address for the implementation contract 140: */ 149: modifier onlyGuardian() {
There are 1 instances of this issue:
<details> <summary>see instances</summary></details>File: ./contracts/ousg/rOUSGFactory.sol //@audit `rOUSGDeployed` is not in CamelCase 141: event rOUSGDeployed( 142: address proxy, 143: address proxyAdmin, 144: address implementation, 145: string name, 146: string ticker 147: );
immutable
rather than constant
While it does not save gas for some simple binary expressions because the compiler knows that developers often make this mistake, it's still best to use the right tool for the task at hand. There is a difference between constant
variables and immutable
variables, and they should each be used in their appropriate contexts. constants
should be used for literal values written into the code, and immutable
variables should be used for expressions, or values calculated in, or passed into the constructor.
There are 6 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 57: bytes32 public constant CONFIGURER_ROLE = keccak256("CONFIGURER_ROLE"); 60: bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
File: ./contracts/ousg/rOUSG.sol 93: bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); 94: bytes32 public constant BURNER_ROLE = keccak256("BURN_ROLE"); 95: bytes32 public constant CONFIGURER_ROLE = keccak256("CONFIGURER_ROLE");
</details>File: ./contracts/ousg/rOUSGFactory.sol 38: bytes32 public constant DEFAULT_ADMIN_ROLE = bytes32(0);
There are 3 instances of this issue:
<details> <summary>see instances</summary></details>File: ./contracts/ousg/rOUSG.sol 100: } 101: 102: function initialize( 110: } 111: 112: function __rOUSG_init( 126: } 127: 128: function __rOUSG_init_unchained(
require()
/revert()
as well as custom errorsConsider using just one method in a single file. The below instances represents the less used technique
There are 2 instances of this issue:
<details> <summary>see instances</summary></details>File: ./contracts/ousg/rOUSG.sol 435: revert UnwrapTooSmall(); 630: revert UnwrapTooSmall();
AccessControlDefaultAdminRules
rather than AccessControl
These contracts inherits from the OpenZeppelin's AccessControl library. However, this library does not follow some security best practices, for example, the DEFAULT_ADMIN_ROLE is also its own admin, meaning it has permissions to grant and revoke this role ref. \nConsider following security best practices and OpenZeppelin's recommendations, and use the AccessControlDefaultAdminRules extension to enforce additional security measures over this role.ref
There are 1 instances of this issue:
<details> <summary>see instances</summary></details>File: ./contracts/ousg/ousgInstantManager.sol 49: contract OUSGInstantManager is 50: ReentrancyGuard,
immutable
variable names don't follow the Solidity style guideFor immutable
variable names, each word should use all capital letters, with underscores separating each word (CONSTANT_CASE)
There are 8 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 72: IERC20 public immutable usdc; // 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; 75: IRWALike public immutable ousg; 78: ROUSG public immutable rousg; 81: IERC20 public immutable buidl; 84: IBUIDLRedeemer public immutable buidlRedeemer; 87: uint256 public immutable decimalsMultiplier; 90: address public immutable usdcReceiver;
link , link, link, link, link, link, link
</details>File: ./contracts/ousg/rOUSGFactory.sol 40: address internal immutable guardian;
function func(address a, address)
-> function func(address a, address /* b */)
There are 14 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol //@audit parameter number 0 starting from left need inline comment 335: function redeem( //@audit parameter number 0 starting from left need inline comment 362: function redeemRebasingOUSG( //@audit parameter number 0 starting from left need inline comment 581: function setMinimumDepositAmount( //@audit parameter number 0 starting from left need inline comment 599: function setMinimumRedemptionAmount( //@audit parameter number 0 starting from left need inline comment 650: function setFeeReceiver(
File: ./contracts/ousg/rOUSG.sol //@audit parameter number 2 starting from left need inline comment 276: function transferFrom( //@audit parameter number 1 starting from left need inline comment 328: function decreaseAllowance( //@audit parameter number 0 starting from left need inline comment 415: function wrap(uint256 _OUSGAmount) external whenNotPaused { //@audit parameter number 0 starting from left need inline comment 431: function unwrap(uint256 _rOUSGAmount) external whenNotPaused { //@audit parameter number 0 starting from left need inline comment //@audit parameter number 1 starting from left need inline comment 472: function _approve( //@audit parameter number 0 starting from left need inline comment //@audit parameter number 1 starting from left need inline comment 501: function _transferShares( //@audit parameter number 0 starting from left need inline comment 529: function _mintShares( //@audit parameter number 0 starting from left need inline comment 554: function _burnShares( //@audit parameter number 2 starting from left need inline comment 586: function _beforeTokenTransfer(
link , link, link, link, link, link, link, link, link
</details>Using the latest solidity version will help avoid old compiler related vulnerabilities
There are 3 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 16: pragma solidity 0.8.16;
File: ./contracts/ousg/rOUSG.sol 16: pragma solidity 0.8.16;
</details>File: ./contracts/ousg/rOUSGFactory.sol 16: pragma solidity 0.8.16;
Consider using formal verification to mathematically prove that your code does what is intended, and does not have any edge cases with unexpected behavior. The solidity compiler itself has this functionality built in
There are 1 instances of this issue:
<details> <summary>see instances</summary></details>File: ./contracts/ousg/ousgInstantManager.sol @audit Should implement invariant tests 1:
Adding a zero address check for each address type parameter can prevent errors.
There are 3 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/rOUSG.sol //@audit _owner, _spender, are not checked 231: function allowance( 232: address _owner, 233: address _spender 234: ) public view returns (uint256) { //@audit _account, are not checked 487: function _sharesOf(address _account) internal view returns (uint256) {
</details>File: ./contracts/ousg/rOUSGFactory.sol //@audit _guardian, are not checked 47: constructor(address _guardian) {
If a function has too many parameters, replacing them with a struct can improve code readability and maintainability, increase reusability, and reduce the likelihood of errors when passing the parameters.
There are 4 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 144: constructor( 145: address defaultAdmin, 146: address _usdc, 147: address _usdcReciever, 148: address _feeReceiver, 149: address _ousgOracle, 150: address _ousg, 151: address _rousg, 152: address _buidl, 153: address _buidlRedeemer, 154: RateLimiterConfig memory rateLimiterConfig 155: ) 156: InstantMintTimeBasedRateLimiter( 157: rateLimiterConfig.mintLimitDuration, 158: rateLimiterConfig.redeemLimitDuration, 159: rateLimiterConfig.mintLimit, 160: rateLimiterConfig.redeemLimit 161: ) 162: { 163: require( 164: address(_usdc) != address(0), 165: "OUSGInstantManager: USDC cannot be 0x0" 166: ); 167: require( 168: address(_usdcReciever) != address(0), 169: "OUSGInstantManager: USDC Receiver cannot be 0x0" 170: ); 171: require( 172: address(_feeReceiver) != address(0), 173: "OUSGInstantManager: feeReceiver cannot be 0x0" 174: ); 175: require( 176: address(_ousgOracle) != address(0), 177: "OUSGInstantManager: OUSG Oracle cannot be 0x0" 178: ); 179: require(_ousg != address(0), "OUSGInstantManager: OUSG cannot be 0x0"); 180: require(_rousg != address(0), "OUSGInstantManager: rOUSG cannot be 0x0"); 181: require(_buidl != address(0), "OUSGInstantManager: BUIDL cannot be 0x0"); 182: require( 183: address(_buidlRedeemer) != address(0), 184: "OUSGInstantManager: BUIDL Redeemer cannot be 0x0" 185: ); 186: require( 187: IERC20Metadata(_ousg).decimals() == IERC20Metadata(_rousg).decimals(), 188: "OUSGInstantManager: OUSG decimals must be equal to USDC decimals" 189: ); 190: require( 191: IERC20Metadata(_usdc).decimals() == IERC20Metadata(_buidl).decimals(), 192: "OUSGInstantManager: USDC decimals must be equal to BUIDL decimals" 193: ); 194: usdc = IERC20(_usdc); 195: usdcReceiver = _usdcReciever; 196: feeReceiver = _feeReceiver; 197: oracle = IRWAOracle(_ousgOracle); 198: ousg = IRWALike(_ousg); 199: rousg = ROUSG(_rousg); 200: buidl = IERC20(_buidl); 201: buidlRedeemer = IBUIDLRedeemer(_buidlRedeemer); 202: decimalsMultiplier = 203: 10 ** 204: (IERC20Metadata(_ousg).decimals() - IERC20Metadata(_usdc).decimals()); 205: require( 206: OUSG_TO_ROUSG_SHARES_MULTIPLIER == 207: rousg.OUSG_TO_ROUSG_SHARES_MULTIPLIER(), 208: "OUSGInstantManager: OUSG to rOUSG shares multiplier must be equal to rOUSG's" 209: ); 210: 211: _grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin); 212: _grantRole(CONFIGURER_ROLE, defaultAdmin); 213: _grantRole(PAUSER_ROLE, defaultAdmin); 214: }
</details>File: ./contracts/ousg/rOUSG.sol 102: function initialize( 103: address _kycRegistry, 104: uint256 requirementGroup, 105: address _ousg, 106: address guardian, 107: address _oracle 108: ) public virtual initializer { 109: __rOUSG_init(_kycRegistry, requirementGroup, _ousg, guardian, _oracle); 110: } 112: function __rOUSG_init( 113: address _kycRegistry, 114: uint256 requirementGroup, 115: address _ousg, 116: address guardian, 117: address _oracle 118: ) internal onlyInitializing { 119: __rOUSG_init_unchained( 120: _kycRegistry, 121: requirementGroup, 122: _ousg, 123: guardian, 124: _oracle 125: ); 126: } 128: function __rOUSG_init_unchained( 129: address _kycRegistry, 130: uint256 _requirementGroup, 131: address _ousg, 132: address guardian, 133: address _oracle 134: ) internal onlyInitializing { 135: __KYCRegistryClientInitializable_init(_kycRegistry, _requirementGroup); 136: ousg = IERC20(_ousg); 137: oracle = IRWAOracle(_oracle); 138: _grantRole(DEFAULT_ADMIN_ROLE, guardian); 139: _grantRole(PAUSER_ROLE, guardian); 140: _grantRole(BURNER_ROLE, guardian); 141: _grantRole(CONFIGURER_ROLE, guardian); 142: }
revert()
/require()
strings for better readabilityCustom errors are available from solidity version 0.8.4. Custom errors are more easily processed in try-catch blocks, and are easier to re-use and maintain.
There are 50 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 163: require( 164: address(_usdc) != address(0), 165: "OUSGInstantManager: USDC cannot be 0x0" 166: ); 167: require( 168: address(_usdcReciever) != address(0), 169: "OUSGInstantManager: USDC Receiver cannot be 0x0" 170: ); 171: require( 172: address(_feeReceiver) != address(0), 173: "OUSGInstantManager: feeReceiver cannot be 0x0" 174: ); 175: require( 176: address(_ousgOracle) != address(0), 177: "OUSGInstantManager: OUSG Oracle cannot be 0x0" 178: ); 179: require(_ousg != address(0), "OUSGInstantManager: OUSG cannot be 0x0"); 180: require(_rousg != address(0), "OUSGInstantManager: rOUSG cannot be 0x0"); 181: require(_buidl != address(0), "OUSGInstantManager: BUIDL cannot be 0x0"); 182: require( 183: address(_buidlRedeemer) != address(0), 184: "OUSGInstantManager: BUIDL Redeemer cannot be 0x0" 185: ); 186: require( 187: IERC20Metadata(_ousg).decimals() == IERC20Metadata(_rousg).decimals(), 188: "OUSGInstantManager: OUSG decimals must be equal to USDC decimals" 189: ); 190: require( 191: IERC20Metadata(_usdc).decimals() == IERC20Metadata(_buidl).decimals(), 192: "OUSGInstantManager: USDC decimals must be equal to BUIDL decimals" 193: ); 205: require( 206: OUSG_TO_ROUSG_SHARES_MULTIPLIER == 207: rousg.OUSG_TO_ROUSG_SHARES_MULTIPLIER(), 208: "OUSGInstantManager: OUSG to rOUSG shares multiplier must be equal to rOUSG's" 209: ); 282: require( 283: IERC20Metadata(address(usdc)).decimals() == 6, 284: "OUSGInstantManager::_mint: USDC decimals must be 6" 285: ); 286: require( 287: usdcAmountIn >= minimumDepositAmount, 288: "OUSGInstantManager::_mint: Deposit amount too small" 289: ); 298: require( 299: usdc.allowance(msg.sender, address(this)) >= usdcAmountIn, 300: "OUSGInstantManager::_mint: Allowance must be given to OUSGInstantManager" 301: ); 310: require( 311: ousgAmountOut > 0, 312: "OUSGInstantManager::_mint: net mint amount can't be zero" 313: ); 344: require( 345: ousg.allowance(msg.sender, address(this)) >= ousgAmountIn, 346: "OUSGInstantManager::redeem: Insufficient allowance" 347: ); 371: require( 372: rousg.allowance(msg.sender, address(this)) >= rousgAmountIn, 373: "OUSGInstantManager::redeemRebasingOUSG: Insufficient allowance" 374: ); 391: require( 392: IERC20Metadata(address(usdc)).decimals() == 6, 393: "OUSGInstantManager::_redeem: USDC decimals must be 6" 394: ); 395: require( 396: IERC20Metadata(address(buidl)).decimals() == 6, 397: "OUSGInstantManager::_redeem: BUIDL decimals must be 6" 398: ); 402: require( 403: usdcAmountToRedeem >= minimumRedemptionAmount, 404: "OUSGInstantManager::_redeem: Redemption amount too small" 405: ); 417: require( 418: usdcAmountOut > 0, 419: "OUSGInstantManager::_redeem: redeem amount can't be zero" 420: ); 459: require( 460: buidl.balanceOf(address(this)) >= minBUIDLRedeemAmount, 461: "OUSGInstantManager::_redeemBUIDL: Insufficient BUIDL balance" 462: ); 466: require( 467: usdc.balanceOf(address(this)) == usdcBalanceBefore + buidlAmountToRedeem, 468: "OUSGInstantManager::_redeemBUIDL: BUIDL:USDC not 1:1" 469: ); 481: require( 482: price > MINIMUM_OUSG_PRICE, 483: "OUSGInstantManager::getOUSGPrice: Price unexpectedly low" 484: ); 557: require(mintFee < 200, "OUSGInstantManager::setMintFee: Fee too high"); 570: require(redeemFee < 200, "OUSGInstantManager::setRedeemFee: Fee too high"); 584: require( 585: _minimumDepositAmount >= FEE_GRANULARITY, 586: "setMinimumDepositAmount: Amount too small" 587: ); 602: require( 603: _minimumRedemptionAmount >= FEE_GRANULARITY, 604: "setMinimumRedemptionAmount: Amount too small" 605: ); 653: require(_feeReceiver != address(0), "FeeReceiver cannot be 0x0"); 757: require(!mintPaused, "OUSGInstantManager: Mint paused"); 763: require(!redeemPaused, "OUSGInstantManager: Redeem paused"); 808: require(success, "Call Failed");
link , link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link
File: ./contracts/ousg/rOUSG.sol 282: require(currentAllowance >= _amount, "TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE"); 333: require( 334: currentAllowance >= _subtractedValue, 335: "DECREASED_ALLOWANCE_BELOW_ZERO" 336: ); 416: require(_OUSGAmount > 0, "rOUSG: can't wrap zero OUSG tokens"); 432: require(_rOUSGAmount > 0, "rOUSG: can't unwrap zero rOUSG tokens"); 477: require(_owner != address(0), "APPROVE_FROM_ZERO_ADDRESS"); 478: require(_spender != address(0), "APPROVE_TO_ZERO_ADDRESS"); 506: require(_sender != address(0), "TRANSFER_FROM_THE_ZERO_ADDRESS"); 507: require(_recipient != address(0), "TRANSFER_TO_THE_ZERO_ADDRESS"); 512: require( 513: _sharesAmount <= currentSenderShares, 514: "TRANSFER_AMOUNT_EXCEEDS_BALANCE" 515: ); 533: require(_recipient != address(0), "MINT_TO_THE_ZERO_ADDRESS"); 558: require(_account != address(0), "BURN_FROM_THE_ZERO_ADDRESS"); 563: require(_sharesAmount <= accountShares, "BURN_AMOUNT_EXCEEDS_BALANCE"); 594: require(_getKYCStatus(msg.sender), "rOUSG: 'sender' address not KYC'd"); 599: require(_getKYCStatus(from), "rOUSG: 'from' address not KYC'd"); 604: require(_getKYCStatus(to), "rOUSG: 'to' address not KYC'd");
link , link, link, link, link, link, link, link, link, link, link, link, link, link, link
</details>File: ./contracts/ousg/rOUSGFactory.sol 76: require(!initialized, "ROUSGFactory: rOUSG already deployed"); 150: require(msg.sender == guardian, "ROUSGFactory: You are not the Guardian"); 129: require(success, "Call Failed");
@inheritdoc
for overridden functionsThere are 20 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 219: /** 220: * @notice Calculates fees and triggers minting OUSG for a given amount of USDC 221: * 222: * @dev Please note that the fees are accumulated in `feeReceiver` 223: * 224: * @param usdcAmountIn amount of USDC exchanged for OUSG (in whatever decimals 225: * specifed by usdc token contract) 226: * 227: * @return ousgAmountOut The quantity of OUSG minted for the user 228: * (18 decimals per OUSG contract) 229: */ 243: /** 244: * @notice Calculates fees and triggers minting rOUSG for a given amount of USDC 245: * 246: * @dev Please note that the fees are accumulated in `feeReceiver` 247: * 248: * @param usdcAmountIn amount of USDC exchanged for rOUSG (in whatever decimals 249: * specifed by usdc token contract) 250: * 251: * @return rousgAmountOut The quantity of rOUSG minted for the user 252: * (18 decimals per rOUSG contract) 253: */ 326: /** 327: * @notice Calculates fees and triggers a redemption of OUSG for a given amount of USDC 328: * 329: * @dev Please note that the fees are accumulated in `feeReceiver` 330: * 331: * @param ousgAmountIn Amount of OUSG to redeem 332: * 333: * @return usdcAmountOut The amount of USDC returned to the user 334: */ 353: /** 354: * @notice Calculates fees and triggers minting rOUSG for a given amount of USDC 355: * 356: * @dev Please note that the fees are actually accumulated in `feeReceiver` 357: * 358: * @param rousgAmountIn Amount of rOUSG to redeem 359: * 360: * @return usdcAmountOut The amount of USDC returned to the user 361: */ 490: /** 491: * @notice Set the mintLimit constraint inside the InstantMintTimeBasedRateLimiter 492: * base contract 493: * 494: * @param _instantMintLimit New limit that dicates how much USDC can be transfered 495: * for minting in a specified duration 496: * (in 6 decimals per the USDC contract) 497: */ 504: /** 505: * @notice Set the redeemLimit constraint inside the InstantMintTimeBasedRateLimiter 506: * base contract 507: * 508: * @param _instantRedemptionLimit New limit that dicates how much USDC 509: * can be redeemed in a specified duration 510: * (in 6 decimals per the USDC contract) 511: */ 518: /** 519: * @notice Sets mintLimitDuration constraint inside the InstantMintTimeBasedRateLimiter 520: * base contract 521: * 522: * @param _instantMintLimitDuration New limit that specifies the interval 523: * (in seconds) in which only `mintLimit` USDC 524: * can be used for minting within 525: */ 532: /** 533: * @notice Sets redeemLimitDuration inside the InstantMintTimeBasedRateLimiter 534: * base contract 535: * 536: * @param _instantRedemptionLimitDuratioin New limit that specifies the interval 537: * (in seconds) in which only `redeemLimit` USDC 538: * can be redeemed within 539: */ 549: /** 550: * @notice Sets the mint fee 551: * 552: * @param _mintFee new mint fee specified in basis points 553: */ 562: /** 563: * @notice Sets the redeem fee. 564: * 565: * @param _redeemFee new redeem fee specified in basis points 566: */ 575: /** 576: * @notice Admin function to set the minimum amount required for a deposit 577: * 578: * @param _minimumDepositAmount The minimum amount required to submit a deposit 579: * request 580: */ 593: /** 594: * @notice Admin function to set the minimum amount to redeem 595: * 596: * @param _minimumRedemptionAmount The minimum amount required to submit a 597: * redemption request 598: */ 617: /** 618: * @notice Admin function to set the minimum amount required to redeem BUIDL 619: * 620: * @param _minimumBUIDLRedemptionAmount The minimum amount required to redeem BUIDL 621: */ 632: /** 633: * @notice Admin function to set the oracle address 634: * 635: * @param _oracle The address of the oracle that provides the OUSG price 636: * in USDC 637: */ 645: /** 646: * @notice Admin function to set the fee receiver address 647: 648: * @param _feeReceiver The address to receive the mint and redemption fees 649: */ 658: /** 659: * @notice Admin function to set the optional investor-based rate limiter 660: * 661: * @param _investorBasedRateLimiter The address of the investor-based rate limiter contract 662: */ 794: function multiexcall(
link , link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link
File: ./contracts/ousg/rOUSG.sol 650: function setKYCRegistry( 656: function setKYCRequirementGroup(
</details>File: ./contracts/ousg/rOUSGFactory.sol 110: /** 111: * @notice Allows for arbitrary batched calls 112: * 113: * @dev All external calls made through this function will 114: * msg.sender == contract address 115: * 116: * @param exCallData Struct consisting of 117: * 1) target - contract to call 118: * 2) data - data to call target with 119: * 3) value - eth value to call target with 120: */
Well-organized data structures make code reviews easier, which may lead to fewer bugs. Consider combining related mappings into mappings to structs, so it's clear what data is related.
There are 2 instances of this issue:
<details> <summary>see instances</summary></details>File: ./contracts/ousg/rOUSG.sol 72: mapping(address => uint256) private shares; 75: mapping(address => mapping(address => uint256)) private allowances;
Use events to signal significant changes to off-chain monitoring tools.
There are 3 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 144: constructor( 145: address defaultAdmin, 146: address _usdc, 147: address _usdcReciever, 148: address _feeReceiver, 149: address _ousgOracle, 150: address _ousg, 151: address _rousg, 152: address _buidl, 153: address _buidlRedeemer, 154: RateLimiterConfig memory rateLimiterConfig 155: ) 156: InstantMintTimeBasedRateLimiter( 157: rateLimiterConfig.mintLimitDuration, 158: rateLimiterConfig.redeemLimitDuration, 159: rateLimiterConfig.mintLimit, 160: rateLimiterConfig.redeemLimit 161: ) 162: { 163: require( 164: address(_usdc) != address(0), 165: "OUSGInstantManager: USDC cannot be 0x0" 166: ); 167: require( 168: address(_usdcReciever) != address(0), 169: "OUSGInstantManager: USDC Receiver cannot be 0x0" 170: ); 171: require( 172: address(_feeReceiver) != address(0), 173: "OUSGInstantManager: feeReceiver cannot be 0x0" 174: ); 175: require( 176: address(_ousgOracle) != address(0), 177: "OUSGInstantManager: OUSG Oracle cannot be 0x0" 178: ); 179: require(_ousg != address(0), "OUSGInstantManager: OUSG cannot be 0x0"); 180: require(_rousg != address(0), "OUSGInstantManager: rOUSG cannot be 0x0"); 181: require(_buidl != address(0), "OUSGInstantManager: BUIDL cannot be 0x0"); 182: require( 183: address(_buidlRedeemer) != address(0), 184: "OUSGInstantManager: BUIDL Redeemer cannot be 0x0" 185: ); 186: require( 187: IERC20Metadata(_ousg).decimals() == IERC20Metadata(_rousg).decimals(), 188: "OUSGInstantManager: OUSG decimals must be equal to USDC decimals" 189: ); 190: require( 191: IERC20Metadata(_usdc).decimals() == IERC20Metadata(_buidl).decimals(), 192: "OUSGInstantManager: USDC decimals must be equal to BUIDL decimals" 193: ); 194: usdc = IERC20(_usdc); 195: usdcReceiver = _usdcReciever; 196: feeReceiver = _feeReceiver; 197: oracle = IRWAOracle(_ousgOracle); 198: ousg = IRWALike(_ousg); 199: rousg = ROUSG(_rousg); 200: buidl = IERC20(_buidl); 201: buidlRedeemer = IBUIDLRedeemer(_buidlRedeemer); 202: decimalsMultiplier = 203: 10 ** 204: (IERC20Metadata(_ousg).decimals() - IERC20Metadata(_usdc).decimals()); 205: require( 206: OUSG_TO_ROUSG_SHARES_MULTIPLIER == 207: rousg.OUSG_TO_ROUSG_SHARES_MULTIPLIER(), 208: "OUSGInstantManager: OUSG to rOUSG shares multiplier must be equal to rOUSG's" 209: ); 210: 211: _grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin); 212: _grantRole(CONFIGURER_ROLE, defaultAdmin); 213: _grantRole(PAUSER_ROLE, defaultAdmin); 214: }
File: ./contracts/ousg/rOUSG.sol 098: constructor() { 099: _disableInitializers(); 100: }
</details>File: ./contracts/ousg/rOUSGFactory.sol 47: constructor(address _guardian) { 48: guardian = _guardian; 49: }
Large and/or complex functions should include comments to make them easier to understand and reduce margin for error.
There are 3 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 254: function mintRebasingOUSG( 362: function redeemRebasingOUSG(
</details>File: ./contracts/ousg/rOUSGFactory.sol 70: function deployRebasingOUSG(
verbatim
blocks are considered identical by deduplicator and can incorrectly be unifiedThe block deduplicator is a step of the opcode-based optimizer which identifies equivalent assembly blocks and merges them into a single one. However, when blocks contained verbatim
, their comparison was performed incorrectly, leading to the collapse of assembly blocks which are identical except for the contents of the verbatim
items. Since verbatim
is only available in Yul, compilation of Solidity sources is not affected. For more details check the following link
There are 3 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 16: pragma solidity 0.8.16;
File: ./contracts/ousg/rOUSG.sol 16: pragma solidity 0.8.16;
</details>File: ./contracts/ousg/rOUSGFactory.sol 16: pragma solidity 0.8.16;
@param
is missing from error
There are 31 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 278: function _mint( 388: function _redeem( 458: function _redeemBUIDL(uint256 buidlAmountToRedeem) internal { 731: /** 732: * @notice Scale provided amount up by `decimalsMultiplier` 733: * 734: * @dev This helper is used for converting a USDC amount's decimals 735: * representation to the rOUSG/OUSG decimals representation. 736: */ 741: /** 742: * @notice Scale provided amount down by `decimalsMultiplier` 743: * 744: * @dev This helper is used for converting an rOUSG/OUSG amount's decimals 745: * representation to the USDC decimals representation. 746: */ 794: function multiexcall(
link , link, link, link, link, link
File: ./contracts/ousg/rOUSG.sol 102: function initialize( 112: function __rOUSG_init( 128: function __rOUSG_init_unchained( 144: /** 145: * @notice An executed shares transfer from `sender` to `recipient`. 146: * 147: * @dev emitted in pair with an ERC20-defined `Transfer` event. 148: */ 193: /** 194: * @return the amount of tokens owned by the `_account`. 195: * 196: * @dev Balances are dynamic and equal the `_account`'s OUSG shares multiplied 197: * by the price of OUSG 198: */ 205: /** 206: * @notice Moves `_amount` tokens from the caller's account to the `_recipient` account. 207: * 208: * @return a boolean value indicating whether the operation succeeded. 209: * Emits a `Transfer` event. 210: * Emits a `TransferShares` event. 211: * 212: * Requirements: 213: * 214: * - `_recipient` cannot be the zero address. 215: * - the caller must have a balance of at least `_amount`. 216: * - the contract must not be paused. 217: * 218: * @dev The `_amount` argument is the amount of tokens, not shares. 219: */ 225: /** 226: * @return the remaining number of tokens that `_spender` is allowed to spend 227: * on behalf of `_owner` through `transferFrom`. This is zero by default. 228: * 229: * @dev This value changes when `approve` or `transferFrom` is called. 230: */ 238: /** 239: * @notice Sets `_amount` as the allowance of `_spender` over the caller's tokens. 240: * 241: * @return a boolean value indicating whether the operation succeeded. 242: * Emits an `Approval` event. 243: * 244: * Requirements: 245: * 246: * - `_spender` cannot be the zero address. 247: * - the contract must not be paused. 248: * 249: * @dev The `_amount` argument is the amount of tokens, not shares. 250: */ 256: /** 257: * @notice Moves `_amount` tokens from `_sender` to `_recipient` using the 258: * allowance mechanism. `_amount` is then deducted from the caller's 259: * allowance. 260: * 261: * @return a boolean value indicating whether the operation succeeded. 262: * 263: * Emits a `Transfer` event. 264: * Emits a `TransferShares` event. 265: * Emits an `Approval` event indicating the updated allowance. 266: * 267: * Requirements: 268: * 269: * - `_sender` and `_recipient` cannot be the zero addresses. 270: * - `_sender` must have a balance of at least `_amount`. 271: * - the caller must have allowance for `_sender`'s tokens of at least `_amount`. 272: * - the contract must not be paused. 273: * 274: * @dev The `_amount` argument is the amount of tokens, not shares. 275: */ 289: /** 290: * @notice Atomically increases the allowance granted to `_spender` by the caller by `_addedValue`. 291: * 292: * This is an alternative to `approve` that can be used as a mitigation for 293: * problems described in: 294: * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol#L42 295: * Emits an `Approval` event indicating the updated allowance. 296: * 297: * Requirements: 298: * 299: * - `_spender` cannot be the the zero address. 300: * - the contract must not be paused. 301: */ 314: /** 315: * @notice Atomically decreases the allowance granted to `_spender` by the caller by `_subtractedValue`. 316: * 317: * This is an alternative to `approve` that can be used as a mitigation for 318: * problems described in: 319: * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol#L42 320: * Emits an `Approval` event indicating the updated allowance. 321: * 322: * Requirements: 323: * 324: * - `_spender` cannot be the zero address. 325: * - `_spender` must have allowance for the caller of at least `_subtractedValue`. 326: * - the contract must not be paused. 327: */ 351: /** 352: * @return the amount of shares owned by `_account`. 353: * 354: * @dev This is the equivalent to the amount of OUSG wrapped by `_account`. 355: */ 360: /** 361: * @return the amount of shares that corresponds to `_rOUSGAmount` of rOUSG 362: */ 370: /** 371: * @return the amount of rOUSG that corresponds to `_shares` of OUSG. 372: */ 382: /** 383: * @notice Moves `_sharesAmount` token shares from the caller's account to the `_recipient` account. 384: * 385: * @return amount of transferred tokens. 386: * Emits a `TransferShares` event. 387: * Emits a `Transfer` event. 388: * 389: * Requirements: 390: * 391: * - `_recipient` cannot be the zero address. 392: * - the caller must have at least `_sharesAmount` shares. 393: * - the contract must not be paused. 394: * 395: * @dev The `_sharesAmount` argument is the amount of shares, not tokens. 396: */ 445: /** 446: * @notice Moves `_amount` tokens from `_sender` to `_recipient`. 447: * Emits a `Transfer` event. 448: * Emits a `TransferShares` event. 449: */ 461: /** 462: * @notice Sets `_amount` as the allowance of `_spender` over the `_owner` s tokens. 463: * 464: * Emits an `Approval` event. 465: * 466: * Requirements: 467: * 468: * - `_owner` cannot be the zero address. 469: * - `_spender` cannot be the zero address. 470: * - the contract must not be paused. 471: */ 484: /** 485: * @return the amount of shares owned by `_account`. 486: */ 491: /** 492: * @notice Moves `_sharesAmount` shares from `_sender` to `_recipient`. 493: * 494: * Requirements: 495: * 496: * - `_sender` cannot be the zero address. 497: * - `_recipient` cannot be the zero address. 498: * - `_sender` must hold at least `_sharesAmount` shares. 499: * - the contract must not be paused. 500: */ 521: /** 522: * @notice Creates `_sharesAmount` shares and assigns them to `_recipient`, increasing the total amount of shares. 523: * 524: * Requirements: 525: * 526: * - `_recipient` cannot be the zero address. 527: * - the contract must not be paused. 528: */ 544: /** 545: * @notice Destroys `_sharesAmount` shares from `_account`'s holdings, decreasing the total amount of shares. 546: * @dev This doesn't decrease the token total supply. 547: * 548: * Requirements: 549: * 550: * - `_account` cannot be the zero address. 551: * - `_account` must hold at least `_sharesAmount` shares. 552: * - the contract must not be paused. 553: */ 572: /** 573: * @dev Hook that is called before any transfer of tokens. This includes 574: * minting and burning. 575: * 576: * Calling conditions: 577: * 578: * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens 579: * will be transferred to `to`. 580: * - when `from` is zero, `amount` tokens will be minted for `to`. 581: * - when `to` is zero, `amount` of ``from``'s tokens will be burned. 582: * - `from` and `to` are never both zero. 583: * 584: * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. 585: */ 650: function setKYCRegistry( 656: function setKYCRequirementGroup(
link , link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link
</details>File: ./contracts/ousg/rOUSGFactory.sol 47: constructor(address _guardian) {
These contracts import some OpenZeppelin libraries, but they are using an old version.
There are 11 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 18: import "contracts/external/openzeppelin/contracts/access/AccessControlEnumerable.sol"; 19: import "contracts/external/openzeppelin/contracts/security/ReentrancyGuard.sol"; 20: import "contracts/external/openzeppelin/contracts/token/IERC20Metadata.sol";
File: ./contracts/ousg/rOUSG.sol 18: import "contracts/external/openzeppelin/contracts/token/IERC20.sol"; 19: import "contracts/external/openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; 20: import "contracts/external/openzeppelin/contracts-upgradeable/token/ERC20/IERC20MetadataUpgradeable.sol"; 21: import "contracts/external/openzeppelin/contracts-upgradeable/proxy/Initializable.sol"; 22: import "contracts/external/openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"; 23: import "contracts/external/openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; 24: import "contracts/external/openzeppelin/contracts-upgradeable/access/AccessControlEnumerableUpgradeable.sol";
link , link, link, link, link, link, link
</details>File: ./contracts/ousg/rOUSGFactory.sol 19: import "contracts/external/openzeppelin/contracts/proxy/ProxyAdmin.sol";
prb-math
from dependenciesprb-math
is an important mathematical library The package.json configuration file says that the project is using an old version of prb-math
.
There are 1 instances of this issue:
<details> <summary>see instances</summary></details>File: package.json //@audit `prb-math` version is 1:
name
Field of package.jsonpackage.json name variable should only consist of lowercase letters and underscores
There are 1 instances of this issue:
<details> <summary>see instances</summary></details>File: package.json //@audit package.json name is ondo-cash-flux 1: {
In Solidity, private
state variables are strictly confined to the contract they are defined in and can't be accessed or modified by its derived contracts. While this offers strong encapsulation, it can limit contract extensibility and modification in inheritance chains. On the other hand, internal
variables can be accessed and potentially overridden by child contracts, granting more flexibility in contract development and upgrades. Therefore, it's recommended to use private
only when you explicitly want to prevent child contract access. Otherwise, prefer internal
to maintain a balance between encapsulation and the flexibility offered by inheritance patterns in Solidity.
There are 3 instances of this issue:
<details> <summary>see instances</summary></details>File: ./contracts/ousg/rOUSG.sol 72: mapping(address => uint256) private shares; 75: mapping(address => mapping(address => uint256)) private allowances; 78: uint256 private totalShares;
Utilizing low-level calls like .call{value: value}
for Ether transfers in Ethereum can be risky, as it can inadvertently allow malicious contract executions through fallback functions. To mitigate these risks and ensure safer Ether transfers, it is recommended to adopt more secure and explicit methods provided by reputable libraries such as OpenZeppelin. Functions like Address.sendValue()
from OpenZeppelin provide a clearer and safer alternative for sending Ether, as they encapsulate necessary checks and error handling, ensuring that Ether is transferred securely and any errors are appropriately dealt with. This not only enhances the security of your smart contract but also improves code readability and maintainability, aligning with modern Solidity development practices.
There are 2 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 805: (bool success, bytes memory ret) = address(exCallData[i].target).call{ 806: value: exCallData[i].value 807: }(exCallData[i].data);
</details>File: ./contracts/ousg/rOUSGFactory.sol 126: (bool success, bytes memory ret) = address(exCallData[i].target).call{ 127: value: exCallData[i].value 128: }(exCallData[i].data);
There are 10 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 194: usdc = IERC20(_usdc); 195: usdcReceiver = _usdcReciever; 196: feeReceiver = _feeReceiver; 197: oracle = IRWAOracle(_ousgOracle); 198: ousg = IRWALike(_ousg); 199: rousg = ROUSG(_rousg); 200: buidl = IERC20(_buidl); 201: buidlRedeemer = IBUIDLRedeemer(_buidlRedeemer); 202: decimalsMultiplier = 203: 10 ** 204: (IERC20Metadata(_ousg).decimals() - IERC20Metadata(_usdc).decimals());
link , link, link, link, link, link, link, link, link
</details>File: ./contracts/ousg/rOUSGFactory.sol 48: guardian = _guardian;
Function call arguments in Yul are evaluated right to left. This order matters when the argument expressions have side-effects, and changing it may change contract behavior. FullInliner is an optimizer step that can replace a function call with the body of that function. The transformation involves assigning argument expressions to temporary variables, which imposes an explicit evaluation order. FullInliner was written with the assumption that this order does not necessarily have to match usual argument evaluation order because the argument expressions have no side-effects. In most circumstances this assumption is true because the default optimization step sequence contains the ExpressionSplitter step. ExpressionSplitter ensures that the code is in expression-split form, which means that function calls cannot appear nested inside expressions, and all function call arguments have to be variables. The assumption is, however, not guaranteed to be true in general. Version 0.6.7 introduced a setting allowing users to specify an arbitrary optimization step sequence, making it possible for the FullInliner to actually encounter argument expressions with side-effects, which can result in behavior differences between optimized and unoptimized bytecode. Contracts compiled without optimization or with the default optimization sequence are not affected. To trigger the bug the user has to explicitly choose compiler settings that contain a sequence with FullInliner step not preceded by ExpressionSplitter. Ref
There are 3 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 16: pragma solidity 0.8.16;
File: ./contracts/ousg/rOUSG.sol 16: pragma solidity 0.8.16;
</details>File: ./contracts/ousg/rOUSGFactory.sol 16: pragma solidity 0.8.16;
Using named returns makes the code more self-documenting, makes it easier to fill out NatSpec, and in some cases can save gas. The cases below are where there currently is at most one return statement, which is ideal for named returns.
There are 24 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 713: function _getInstantMintFees( 714: uint256 usdcAmount 715: ) internal view returns (uint256) { 725: function _getInstantRedemptionFees( 726: uint256 usdcAmount 727: ) internal view returns (uint256) { 737: function _scaleUp(uint256 amount) internal view returns (uint256) { 747: function _scaleDown(uint256 amount) internal view returns (uint256) {
File: ./contracts/ousg/rOUSG.sol 166: function name() public pure returns (string memory) { 174: function symbol() public pure returns (string memory) { 181: function decimals() public pure returns (uint8) { 188: function totalSupply() public view returns (uint256) { 199: function balanceOf(address _account) public view returns (uint256) { 220: function transfer(address _recipient, uint256 _amount) public returns (bool) { 231: function allowance( 232: address _owner, 233: address _spender 234: ) public view returns (uint256) { 251: function approve(address _spender, uint256 _amount) public returns (bool) { 276: function transferFrom( 277: address _sender, 278: address _recipient, 279: uint256 _amount 280: ) public returns (bool) { 302: function increaseAllowance( 303: address _spender, 304: uint256 _addedValue 305: ) public returns (bool) { 328: function decreaseAllowance( 329: address _spender, 330: uint256 _subtractedValue 331: ) public returns (bool) { 347: function getTotalShares() public view returns (uint256) { 356: function sharesOf(address _account) public view returns (uint256) { 363: function getSharesByROUSG( 364: uint256 _rOUSGAmount 365: ) public view returns (uint256) { 373: function getROUSGByShares(uint256 _shares) public view returns (uint256) { 397: function transferShares( 398: address _recipient, 399: uint256 _sharesAmount 400: ) public returns (uint256) { 487: function _sharesOf(address _account) internal view returns (uint256) { 529: function _mintShares( 530: address _recipient, 531: uint256 _sharesAmount 532: ) internal whenNotPaused returns (uint256) { 554: function _burnShares( 555: address _account, 556: uint256 _sharesAmount 557: ) internal whenNotPaused returns (uint256) {
link , link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link
</details>File: ./contracts/ousg/rOUSGFactory.sol 70: function deployRebasingOUSG( 71: address kycRegistry, 72: uint256 requirementGroup, 73: address ousg, 74: address oracle 75: ) external onlyGuardian returns (address, address, address) {
There are 24 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 57: bytes32 public constant CONFIGURER_ROLE = keccak256("CONFIGURER_ROLE"); 60: bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); 808: require(success, "Call Failed");
File: ./contracts/ousg/rOUSG.sol 95: bytes32 public constant CONFIGURER_ROLE = keccak256("CONFIGURER_ROLE"); 93: bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); 94: bytes32 public constant BURNER_ROLE = keccak256("BURN_ROLE"); 167: return "Rebasing OUSG"; 175: return "rOUSG"; 282: require(currentAllowance >= _amount, "TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE"); 335: "DECREASED_ALLOWANCE_BELOW_ZERO" 416: require(_OUSGAmount > 0, "rOUSG: can't wrap zero OUSG tokens"); 432: require(_rOUSGAmount > 0, "rOUSG: can't unwrap zero rOUSG tokens"); 477: require(_owner != address(0), "APPROVE_FROM_ZERO_ADDRESS"); 478: require(_spender != address(0), "APPROVE_TO_ZERO_ADDRESS"); 506: require(_sender != address(0), "TRANSFER_FROM_THE_ZERO_ADDRESS"); 507: require(_recipient != address(0), "TRANSFER_TO_THE_ZERO_ADDRESS"); 514: "TRANSFER_AMOUNT_EXCEEDS_BALANCE" 533: require(_recipient != address(0), "MINT_TO_THE_ZERO_ADDRESS"); 558: require(_account != address(0), "BURN_FROM_THE_ZERO_ADDRESS"); 563: require(_sharesAmount <= accountShares, "BURN_AMOUNT_EXCEEDS_BALANCE"); 594: require(_getKYCStatus(msg.sender), "rOUSG: 'sender' address not KYC'd"); 599: require(_getKYCStatus(from), "rOUSG: 'from' address not KYC'd"); 604: require(_getKYCStatus(to), "rOUSG: 'to' address not KYC'd");
link , link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link
</details>File: ./contracts/ousg/rOUSGFactory.sol 129: require(success, "Call Failed");
Consider whether reasonable bounds checks for variables would be useful
There are 5 instances of this issue:
<details> <summary>see instances</summary></details>File: ./contracts/ousg/ousgInstantManager.sol 559: mintFee = _mintFee; 572: redeemFee = _redeemFee; 590: minimumDepositAmount = _minimumDepositAmount; 610: minimumRedemptionAmount = _minimumRedemptionAmount; 629: minBUIDLRedeemAmount = _minimumBUIDLRedemptionAmount;
There are 25 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol //@audit these parameters `to`, are not checked 278: function _mint( 279: uint256 usdcAmountIn, 280: address to //@audit these parameters `_oracle`, are not checked 638: function setOracle( 639: address _oracle //@audit these parameters ``, are not checked 650: function setFeeReceiver( 651: address _feeReceiver //@audit these parameters `_investorBasedRateLimiter`, are not checked 663: function setInvestorBasedRateLimiter( 664: address _investorBasedRateLimiter //@audit these parameters `token`,`to`, are not checked 819: function retrieveTokens( 820: address token, 821: address to, 822: uint256 amount
File: ./contracts/ousg/rOUSG.sol //@audit these parameters `_kycRegistry`,`_ousg`,`guardian`,`_oracle`, are not checked 102: function initialize( 103: address _kycRegistry, 104: uint256 requirementGroup, 105: address _ousg, 106: address guardian, 107: address _oracle //@audit these parameters `_kycRegistry`,`_ousg`,`guardian`,`_oracle`, are not checked 112: function __rOUSG_init( 113: address _kycRegistry, 114: uint256 requirementGroup, 115: address _ousg, 116: address guardian, 117: address _oracle //@audit these parameters `_kycRegistry`,`_ousg`,`guardian`,`_oracle`, are not checked 128: function __rOUSG_init_unchained( 129: address _kycRegistry, 130: uint256 _requirementGroup, 131: address _ousg, 132: address guardian, 133: address _oracle //@audit these parameters `_recipient`, are not checked 220: function transfer(address _recipient, uint256 _amount) public returns (bool) { //@audit these parameters `_owner`,`_spender`, are not checked 231: function allowance( 232: address _owner, 233: address _spender //@audit these parameters `_spender`, are not checked 251: function approve(address _spender, uint256 _amount) public returns (bool) { //@audit these parameters `_sender`,`_recipient`, are not checked 276: function transferFrom( 277: address _sender, 278: address _recipient, 279: uint256 _amount //@audit these parameters `_spender`, are not checked 328: function decreaseAllowance( 329: address _spender, 330: uint256 _subtractedValue //@audit these parameters `_account`, are not checked 356: function sharesOf(address _account) public view returns (uint256) { //@audit these parameters `_recipient`, are not checked 397: function transferShares( 398: address _recipient, 399: uint256 _sharesAmount //@audit these parameters `_sender`,`_recipient`, are not checked 450: function _transfer( 451: address _sender, 452: address _recipient, 453: uint256 _amount //@audit these parameters ``,``, are not checked 472: function _approve( 473: address _owner, 474: address _spender, 475: uint256 _amount //@audit these parameters `_account`, are not checked 487: function _sharesOf(address _account) internal view returns (uint256) { //@audit these parameters ``,``, are not checked 501: function _transferShares( 502: address _sender, 503: address _recipient, 504: uint256 _sharesAmount //@audit these parameters ``, are not checked 529: function _mintShares( 530: address _recipient, 531: uint256 _sharesAmount //@audit these parameters ``, are not checked 554: function _burnShares( 555: address _account, 556: uint256 _sharesAmount //@audit these parameters `_oracle`, are not checked 613: function setOracle(address _oracle) external onlyRole(DEFAULT_ADMIN_ROLE) { //@audit these parameters `_account`, are not checked 624: function burn( 625: address _account, 626: uint256 _amount //@audit these parameters `registry`, are not checked 650: function setKYCRegistry( 651: address registry
link , link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link
</details>File: ./contracts/ousg/rOUSGFactory.sol //@audit these parameters `kycRegistry`,`ousg`,`oracle`, are not checked 70: function deployRebasingOUSG( 71: address kycRegistry, 72: uint256 requirementGroup, 73: address ousg, 74: address oracle
address(this)
Consider preventing a contract's tokens from being transferred/minted to the contract itself
There are 3 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 230: function mint( 231: uint256 usdcAmountIn 232: ) 233: external 234: override 235: nonReentrant 236: whenMintNotPaused 237: returns (uint256 ousgAmountOut) 238: { 239: ousgAmountOut = _mint(usdcAmountIn, msg.sender); 240: emit InstantMintOUSG(msg.sender, usdcAmountIn, ousgAmountOut); 241: }
</details>File: ./contracts/ousg/rOUSG.sol 220: function transfer(address _recipient, uint256 _amount) public returns (bool) { 221: _transfer(msg.sender, _recipient, _amount); 222: return true; 223: } 276: function transferFrom( 277: address _sender, 278: address _recipient, 279: uint256 _amount 280: ) public returns (bool) { 281: uint256 currentAllowance = allowances[_sender][msg.sender]; 282: require(currentAllowance >= _amount, "TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE"); 283: 284: _transfer(_sender, _recipient, _amount); 285: _approve(_sender, msg.sender, currentAllowance - _amount); 286: return true; 287: }
Named function calls in Solidity greatly improve code readability by explicitly mapping arguments to their respective parameter names. This clarity becomes critical when dealing with functions that have numerous or complex parameters, reducing potential errors due to misordered arguments. Therefore, adopting named function calls contributes to more maintainable and less error-prone code.
There are 28 instances of this issue:
<details> <summary>see instances</summary>File: ./contracts/ousg/ousgInstantManager.sol 211: _grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin); 212: _grantRole(CONFIGURER_ROLE, defaultAdmin); 213: _grantRole(PAUSER_ROLE, defaultAdmin); 263: uint256 ousgAmountOut = _mint(usdcAmountIn, address(this)); 400: uint256 usdcAmountToRedeem = _getRedemptionAmount(ousgAmountIn, ousgPrice); 239: ousgAmountOut = _mint(usdcAmountIn, msg.sender); 308: ousgAmountOut = _getMintAmount(usdcAmountAfterFee, ousgPrice);
link , link, link, link, link, link, link
File: ./contracts/ousg/rOUSG.sol 109: __rOUSG_init(_kycRegistry, requirementGroup, _ousg, guardian, _oracle); 119: __rOUSG_init_unchained( 120: _kycRegistry, 121: requirementGroup, 122: _ousg, 123: guardian, 124: _oracle 125: ); 135: __KYCRegistryClientInitializable_init(_kycRegistry, _requirementGroup); 138: _grantRole(DEFAULT_ADMIN_ROLE, guardian); 139: _grantRole(PAUSER_ROLE, guardian); 140: _grantRole(BURNER_ROLE, guardian); 141: _grantRole(CONFIGURER_ROLE, guardian); 221: _transfer(msg.sender, _recipient, _amount); 252: _approve(msg.sender, _spender, _amount); 284: _transfer(_sender, _recipient, _amount); 285: _approve(_sender, msg.sender, currentAllowance - _amount); 306: _approve( 307: msg.sender, 308: _spender, 309: allowances[msg.sender][_spender] + _addedValue 310: ); 337: _approve(msg.sender, _spender, currentAllowance - _subtractedValue); 401: _transferShares(msg.sender, _recipient, _sharesAmount); 418: _mintShares(msg.sender, ousgSharesAmount); 436: _burnShares(msg.sender, ousgSharesAmount); 456: _transferShares(_sender, _recipient, _sharesToTransfer); 509: _beforeTokenTransfer(_sender, _recipient, _sharesAmount); 535: _beforeTokenTransfer(address(0), _recipient, _sharesAmount); 560: _beforeTokenTransfer(_account, address(0), _sharesAmount); 632: _burnShares(_account, ousgSharesAmount);
link , link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link, link
</details>#0 - 0xRobocop
2024-04-05T05:34:02Z
Most are bot findings
#1 - c4-pre-sort
2024-04-05T05:34:06Z
0xRobocop marked the issue as insufficient quality report
#2 - c4-judge
2024-04-09T15:52:28Z
3docSec marked the issue as grade-b