Platform: Code4rena
Start Date: 28/11/2022
Pot Size: $192,500 USDC
Total HM: 33
Participants: 106
Period: 11 days
Judge: LSDan
Total Solo HM: 15
Id: 186
League: ETH
Rank: 46/106
Findings: 4
Award: $257.37
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: IllIllI
Also found by: 0xNazgul, Atarpara, Awesome, Aymen0909, BClabs, Kong, ali_shehab, bullseye, chaduke, csanuragjain, datapunk, fatherOfBlocks, hansfriese, kaliberpoziomka8552, nicobevi, pashov, pzeus, shark, unforgiven, web3er, xiaoming90
44.934 USDC - $44.93
Function removeFeeder()
on NFTFloorOracle.sol
does not validate that the sender has the required role DEFAULT_ADMIN_ROLE
allowing anyone to call this function successfully. This could generate unexpected behaviours on the oracle (DDOS attacks blocking feeder updates)
/// @notice Allows owner to remove feeder. /// @param _feeder feeder to remove function removeFeeder(address _feeder) external onlyWhenFeederExisted(_feeder) { _removeFeeder(_feeder); }
Add the validation to this function
/// @notice Allows owner to remove feeder. /// @param _feeder feeder to remove function removeFeeder(address _feeder) external onlyWhenFeederExisted(_feeder) onlyRole(DEFAULT_ADMIN_ROLE) { _removeFeeder(_feeder); }
#0 - c4-judge
2022-12-20T14:05:54Z
dmvt marked the issue as primary issue
#1 - c4-judge
2023-01-09T14:10:21Z
dmvt changed the severity to 3 (High Risk)
#2 - c4-judge
2023-01-23T15:59:46Z
dmvt marked the issue as satisfactory
#3 - C4-Staff
2023-02-01T19:10:37Z
captainmangoC4 marked issue #402 as primary and marked this issue as a duplicate of 402
🌟 Selected for report: carlitox477
Also found by: 0xDave, Jeiwan, Rolezn, __141345__, imare, nicobevi
102.8853 USDC - $102.89
https://github.com/code-423n4/2022-11-paraspace/blob/3e4e2e4e1322482dcdc6d342f8799cd44a3e42f5/paraspace-core/contracts/misc/NFTFloorOracle.sol#L236-L248 https://github.com/code-423n4/2022-11-paraspace/blob/3e4e2e4e1322482dcdc6d342f8799cd44a3e42f5/paraspace-core/contracts/misc/NFTFloorOracle.sol#L195-L216
If an asset is whitelisted in the contract but any oracle has returned a floor price yet, then the call to getPrice()
would return a price 0.
This opens a time window when an attacker could take advantage of the assets with price 0.
Calls to getPrice()
should revert in case that the oracle has no price records yet to avoid any unexpected behaviour until at least one price is registered in the contract.
POC: Foundry test
// SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.10; import "forge-std/Vm.sol"; import "forge-std/Test.sol"; import "forge-std/console.sol"; import {NFTFloorOracle} from "../../../src/paraspace/misc/NFTFloorOracle.sol"; contract NFTFloorOracleTest is Test { NFTFloorOracle private target; address[] private feeders; address[] private invalidFeeders; address[] private assets; address[] private invalidAssets; address private admin; address private attacker; function setUp() public { admin = makeAddr("admin"); feeders.push(makeAddr("feeder1")); feeders.push(makeAddr("feeder2")); feeders.push(makeAddr("feeder3")); feeders.push(makeAddr("feeder4")); invalidFeeders.push(makeAddr("invalidFeeder1")); assets.push(makeAddr("asset1")); assets.push(makeAddr("asset2")); invalidAssets.push(makeAddr("invalidAsset1")); target = new NFTFloorOracle(); target.initialize(admin, feeders, assets); } function test() public { address asset = assets[0]; uint256 assetPrice = target.getPrice(asset); assertEq(assetPrice, 0, "Returned price is 0 for asset"); } }
Solution: Add a validation in the getPrice()
function
/// @param _asset The nft contract /// @return price The most recent price on chain function getPrice(address _asset) external view override returns (uint256 price) { uint256 updatedAt = assetPriceMap[_asset].updatedAt; require(updatedAt > 0, "NFTOracle: asset without price"); // ADD THIS LINE require((block.number - updatedAt) <= config.expirationPeriod, "NFTOracle: asset price expired"); return assetPriceMap[_asset].twap; }
#0 - c4-judge
2022-12-20T14:05:25Z
dmvt marked the issue as primary issue
#1 - c4-judge
2023-01-09T13:52:03Z
dmvt changed the severity to 2 (Med Risk)
#2 - c4-judge
2023-01-23T20:37:47Z
dmvt marked the issue as partial-50
#3 - c4-judge
2023-01-26T15:11:50Z
Simon-Busch marked issue #267 as primary and marked this issue as a duplicate of 267
🌟 Selected for report: IllIllI
Also found by: 0x4non, 0x52, 0xAgro, 0xNazgul, 0xSmartContract, 0xackermann, 9svR6w, Awesome, Aymen0909, B2, BRONZEDISC, Bnke0x0, Deekshith99, Deivitto, Diana, Dravee, HE1M, Jeiwan, Kaiziron, KingNFT, Lambda, Mukund, PaludoX0, RaymondFam, Rolezn, Sathish9098, Secureverse, SmartSek, __141345__, ahmedov, ayeslick, brgltd, cccz, ch0bu, chrisdior4, cryptonue, cryptostellar5, csanuragjain, datapunk, delfin454000, erictee, gz627, gzeon, helios, i_got_hacked, ignacio, imare, jadezti, jayphbee, joestakey, kankodu, ksk2345, ladboy233, martin, nadin, nicobevi, oyc_109, pashov, pavankv, pedr02b2, pzeus, rbserver, ronnyx2017, rvierdiiev, shark, unforgiven, xiaoming90, yjrwkk
103.9175 USDC - $103.92
/// @notice Allow contract creator to set admin and updaters /// @param _admin The admin who can change roles /// @param _feeders The initial updaters /// @param _assets The initial nft assets function initialize( address _admin, address[] memory _feeders, address[] memory _assets ) public initializer { _addAssets(_assets); _addFeeders(_feeders); _setupRole(DEFAULT_ADMIN_ROLE, _admin); //still need to grant update_role to admin for emergency call _setupRole(UPDATER_ROLE, _admin); _setConfig(EXPIRATION_PERIOD, MAX_DEVIATION_RATE); }
change it to
/// @notice Allow contract creator to set admin and updaters /// @param _admin The admin who can change roles /// @param _feeders The initial updaters /// @param _assets The initial nft assets constructor( address _admin, address[] memory _feeders, address[] memory _assets ) { _addAssets(_assets); _addFeeders(_feeders); _setupRole(DEFAULT_ADMIN_ROLE, _admin); //still need to grant update_role to admin for emergency call _setupRole(UPDATER_ROLE, _admin); _setConfig(EXPIRATION_PERIOD, MAX_DEVIATION_RATE); }
and remove the Initializable.sol dependency
onlyWhenFeederExisted()
on removeFeeder()
call.Modifier onlyWhenFeederExisted()
is being called twice when removeFeeder()
is executed. Once from the function itself and one more time in _removeFeeder()
resulting in gas wasted.
Remove the validation from removeFeeder()
function
/// @notice Allows owner to remove feeder. /// @param _feeder feeder to remove function removeFeeder(address _feeder) external // onlyWhenFeederExisted(_feeder) # remove this line { _removeFeeder(_feeder); }
onlyWhenAssetExisted()
on removeAsset()
call.Modifier onlyWhenAssetExisted()
is being called twice when removeAsset()
is executed. Once from the function itself and one more time in _removeAsset()
resulting in gas wasted.
Remove the validation from removeAsset()
function
/// @notice Allows owner to remove asset. /// @param _asset asset to remove function removeAsset(address _asset) external onlyRole(DEFAULT_ADMIN_ROLE) // onlyWhenAssetExisted(_asset) # remove this line { _removeAsset(_asset); }
#0 - c4-judge
2023-01-25T10:42:02Z
dmvt marked the issue as grade-b