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: 17/106
Findings: 2
Award: $1,591.05
๐ Selected for report: 0
๐ Solo Findings: 0
๐ 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
L-R | Issue | Instances |
---|---|---|
[Lโ01] | Open TODO in the code | 1 |
N-C | Issue | Instances |
---|---|---|
[NCโ01] | The nonReentrant modifier should occur before all other modifiers | 4 |
[NCโ02] | Constants should be defined rather than using magic numbers | 2 |
[NCโ03] | 10 ** 18 can be changed to 1e18 for readability | 2 |
[NCโ04] | Typo in comments | 3 |
[NCโ05] | Use latest Solidity version | 1 |
[NCโ06] | Use the latest version of OpenZeppelin | 1 |
TODO
in the codeThere is an open TODO in LooksRareAdapter.sol
and UniswapV3OracleWrapper.sol
- this implies changes that might not be audited. Resolve it or remove it.
Lines of code:
nonReentrant
modifier should occur before all other modifiersThis is a best-practice to protect against reentrancy in other modifiers.
function depositApeCoin(ApeCoinStaking.SingleNft[] calldata _nfts) external onlyPool nonReentrant
function claimApeCoin(uint256[] calldata _nfts, address _recipient) external onlyPool nonReentrant
function withdrawApeCoin( ApeCoinStaking.SingleNft[] calldata _nfts, address _recipient ) external onlyPool nonReentrant
function depositBAKC(ApeCoinStaking.PairNftWithAmount[] calldata _nftPairs) external onlyPool nonReentrant
Developers should define a constant variable for every magic value used , giving it a clear and self-explanatory name.
C4Audit didn't find the below ones:
File: NFTFloorOracle.sol
? (_twap * 100) / _priorTwap
: (_priorTwap * 100) / _twap;
Lines of code:
File: UniswapV3OracleWrapper.sol
(oracleData.token0Price * (10**18))
(10 ** (18 + oracleData.token1Decimal - oracleData.token0Decimal)))
(10 ** (18 + oracleData.token0Decimal - oracleData.token1Decimal)))
File: LiquidationLogic.sol
//userDebt from liquadationReserve
liquidationReserve
*
File: NFTFloorOrcale.sol
/// aggeregate prices
aggregate
*//aggeregate with price
aggregate
*
ParaSpace is currently using Solidity version 0.8.10 - it is a best practice to use latest Solidity version as you can get compiler optimisations and bugfixes.
To prevent any issues in the future (e.g. using solely hardhat to compile and deploy the contracts), upgrade the used OZ packages within the package.json to the latest versions. Currently the project is using 4.2.0.
Upgrade it to stable 4.8.0 (which is the newest)
Lines of code:
#0 - c4-judge
2023-01-25T10:56:43Z
dmvt marked the issue as grade-b
๐ Selected for report: IllIllI
Also found by: B2, Deivitto, Dravee, PaludoX0, RaymondFam, Rolezn, Sathish9098, _Adam, ahmedov, c3phas, chrisdior4, cyberinn, rjs, saneryee
1487.1349 USDC - $1,487.13
When fetching data from a storage location, assigning the data to a memory variable causes all fields of the struct/array to be read from storage, which incurs a Gcoldsload (2100 gas) for each field of the struct/array. If the fields are read from the new memory variable, they incur an additional MLOAD rather than a cheap stack read. Instead of declearing the variable with the memory keyword, declaring the variable with the storage keyword and caching any fields that need to be re-read in stack variables, will be much cheaper, only incuring the Gcoldsload for the fields actually read. The only time it makes sense to read the whole struct/array into a memory variable, is if the full struct/array is being returned by the function, is being passed to a function that requires memory, or if the array/struct is being read from another memory array/struct.
File: LooksRareAdapter.sol
OfferItem[] memory offer = new OfferItem;
ConsiderationItem[] memory consideration = new ConsiderationItem;
Lines of code:
If a function modifier such as onlyOwner is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are CALLVALUE(2),DUP1(3),ISZERO(3),PUSH2(3),JUMPI(10),PUSH1(3),DUP1(3),REVERT(0),JUMPDEST(1),POP(2), which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost
Almost all of the functions in this file have this issue.
File: PoolAddressesProvider.sol
function setMarketId(string memory newMarketId) external override onlyOwner {
function setAddress(bytes32 id, address newAddress) external override onlyOwner {
function setAddressAsProxy(bytes32 id, address newImplementationAddress) external override onlyOwner {
function updatePoolImpl( IParaProxy.ProxyImplementation[] calldata implementationParams, address _init, bytes calldata _calldata ) external override onlyOwner {
function setPoolConfiguratorImpl(address newPoolConfiguratorImpl) external override onlyOwner {
See this issue which describes the fact that there is a larger deployment gas cost, but with enough runtime calls, the change ends up being cheaper by 3 gas:
File: SeaportAdapter.sol
require(
// NOT criteria based and must be basic order
advancedOrders.length == 2 &&
isBasicOrder(advancedOrders[0]) &&
isBasicOrder(advancedOrders[1]),
Errors.INVALID_MARKETPLACE_ORDER
);
Combining mappings of address into a single mapping of address to a struct can save a Gssset (20000 gas) operation per mapping combined. This also makes it cheaper for functions reading and writing several of these mappings by saving a Gkeccak256 operation- 30 gas.
Combine the structs to a single one and create only one mapping from address to the newly created struct.
File: NFTFloorOracle.sol
mapping(address => PriceInformation) public assetPriceMap; mapping(address => FeederPosition) private feederPositionMap; mapping(address => FeederRegistrar) public assetFeederMap;
Lines of code:
Using the addition operator instead of plus-equals saves 113 gas
File: UniswapV3OracleWrapper.sol
token0Amount += positionData.tokensOwed0; token1Amount += positionData.tokensOwed1;
sum += getTokenPrice(tokenIds[index]);
Lines of code:
File: GenericLogic.sol
totalValue += tokenPrice; totalLTV += tmpLTV * tokenPrice; totalValue += _getTokenPrice(
Lines of code:
calldata
instead of memory
for read-only arguments in EXTERNAL FUNCTIONS SAVES GAS - 2File: PoolApeStaking.sol
function withdrawBAKC(
address nftAsset,
ApeCoinStaking.PairNftWithAmount[] memory
_nftPairs
) external nonReentrant {
#0 - c4-judge
2023-01-25T10:56:01Z
dmvt marked the issue as grade-b
#1 - c4-judge
2023-01-26T12:12:33Z
dmvt marked the issue as grade-a