ParaSpace contest - saneryee's results

The First Ever Cross-Margin NFT Financialization Protocol.

General Information

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

ParaSpace

Findings Distribution

Researcher Performance

Rank: 54/106

Findings: 1

Award: $145.94

Gas:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: IllIllI

Also found by: B2, Deivitto, Dravee, PaludoX0, RaymondFam, Rolezn, Sathish9098, _Adam, ahmedov, c3phas, chrisdior4, cyberinn, rjs, saneryee

Labels

bug
G (Gas Optimization)
grade-b
edited-by-warden
G-06

Awards

145.9382 USDC - $145.94

External Links

GASOP Report

IssueInstances
[G-001]Splitting require() statements that use && saves gas11
[G-002]Don't use _msgSender() if not supporting EIP-27713
[G-003]Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate12
[G-004]Using calldata instead of memory for read-only arguments in external functions saves gas8
[G-005]internal functions only called once can be inlined to save gas7
[G-006]Replace modifier with function5
[G-007]Use elementary types or a user-defined type instead of a struct that has only one member1
[G-008]Use named returns for local variables where it is possible.6

[G-001] Splitting require() statements that use && saves gas

Impact

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.

Findings

Total:11

paraspace-core/contracts/protocol/libraries/logic/MarketplaceLogic.sol#L171-175

171:    require(
172:                marketplaceIds.length == payloads.length &&
173:                    payloads.length == credits.length,
174:                Errors.INCONSISTENT_PARAMS_LENGTH
175:            );

paraspace-core/contracts/protocol/libraries/logic/MarketplaceLogic.sol#L279-283

279:    require(
280:                marketplaceIds.length == payloads.length &&
281:                    payloads.length == credits.length,
282:                Errors.INCONSISTENT_PARAMS_LENGTH
283:            );

paraspace-core/contracts/protocol/libraries/logic/ValidationLogic.sol#L546-549

546:    require(
547:                vars.collateralReserveActive && vars.principalReserveActive,
548:                Errors.RESERVE_INACTIVE
549:            );

paraspace-core/contracts/protocol/libraries/logic/ValidationLogic.sol#L636-639

636:    require(
637:                vars.collateralReserveActive && vars.principalReserveActive,
638:                Errors.RESERVE_INACTIVE
639:            );

paraspace-core/contracts/protocol/libraries/logic/ValidationLogic.sol#L550-553

550:    require(
551:                !vars.collateralReservePaused && !vars.principalReservePaused,
552:                Errors.RESERVE_PAUSED
553:            );

paraspace-core/contracts/protocol/libraries/logic/ValidationLogic.sol#L640-643

640:    require(
641:                !vars.collateralReservePaused && !vars.principalReservePaused,
642:                Errors.RESERVE_PAUSED
643:            );

paraspace-core/contracts/protocol/libraries/logic/ValidationLogic.sol#L672-676

672:    require(
673:                params.maxLiquidationAmount >= params.actualLiquidationAmount &&
674:                    (msg.value == 0 || msg.value >= params.maxLiquidationAmount),
675:                Errors.LIQUIDATION_AMOUNT_NOT_ENOUGH
676:            );

paraspace-core/contracts/protocol/libraries/logic/ValidationLogic.sol#L1196-1199

1196:    require(
1197:                    vars.token0IsActive && vars.token1IsActive,
1198:                    Errors.RESERVE_INACTIVE
1199:                );

paraspace-core/contracts/protocol/libraries/logic/ValidationLogic.sol#L1202-1205

1202:    require(
1203:                    !vars.token0IsPaused && !vars.token1IsPaused,
1204:                    Errors.RESERVE_PAUSED
1205:                );

paraspace-core/contracts/protocol/libraries/logic/ValidationLogic.sol#L1208-1211

1208:    require(
1209:                    !vars.token0IsFrozen && !vars.token1IsFrozen,
1210:                    Errors.RESERVE_FROZEN
1211:                );

paraspace-core/contracts/protocol/pool/PoolParameters.sol#L216-220

216:    require(
217:                value > MIN_AUCTION_HEALTH_FACTOR &&
218:                    value <= MAX_AUCTION_HEALTH_FACTOR,
219:                Errors.INVALID_AMOUNT
220:            );

[G-002] Don't use _msgSender() if not supporting EIP-2771

Impact

The use of _msgSender() when there is no implementation of a meta transaction mechanism that uses it, such as EIP-2771, very slightly increases gas consumption.(Review EIP-2771 in contract)

Findings

Total:3

paraspace-core/contracts/protocol/tokenization/NTokenMoonBirds.sol#L99

99:    _isApprovedOrOwner(_msgSender(), tokenIds[index]),

paraspace-core/contracts/protocol/tokenization/base/MintableIncentivizedERC721.sol#L260

paraspace-core/contracts/protocol/tokenization/base/MintableIncentivizedERC721.sol#L297

260:    _isApprovedOrOwner(_msgSender(), tokenId),
...
297:    _isApprovedOrOwner(_msgSender(), tokenId),

[G-003] Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate

Impact

Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations.

Findings

Total:12

paraspace-core/contracts/protocol/configuration/PoolAddressesProvider.sol#L25-28

25:        mapping(bytes32 => address) private _addresses;
26:
27:        // Map of marketplace contracts (id => address)
28:        mapping(bytes32 => DataTypes.Marketplace) internal _marketplaces;

paraspace-core/contracts/protocol/libraries/logic/AuctionLogic.sol#L43-44

43:            mapping(uint256 => address) storage reservesList,
44:            mapping(address => DataTypes.UserConfigurationMap) storage usersConfig,

paraspace-core/contracts/protocol/libraries/logic/AuctionLogic.sol#L103-104

103:            mapping(uint256 => address) storage reservesList,
104:            mapping(address => DataTypes.UserConfigurationMap) storage usersConfig,

paraspace-core/contracts/protocol/libraries/logic/LiquidationLogic.sol#L163-164

163:            mapping(uint256 => address) storage reservesList,
164:            mapping(address => DataTypes.UserConfigurationMap) storage usersConfig,

paraspace-core/contracts/protocol/libraries/logic/LiquidationLogic.sol#L288-289

288:            mapping(uint256 => address) storage reservesList,
289:            mapping(address => DataTypes.UserConfigurationMap) storage usersConfig,

paraspace-core/contracts/protocol/libraries/logic/LiquidationLogic.sol#L488-489

488:            mapping(uint256 => address) storage reservesList,
489:            mapping(address => DataTypes.UserConfigurationMap) storage usersConfig,

paraspace-core/contracts/protocol/libraries/logic/SupplyLogic.sol#L464-465

464:            mapping(uint256 => address) storage reservesList,
465:            mapping(address => DataTypes.UserConfigurationMap) storage usersConfig,

paraspace-core/contracts/protocol/libraries/logic/SupplyLogic.sol#L525-526

525:            mapping(uint256 => address) storage reservesList,
526:            mapping(address => DataTypes.UserConfigurationMap) storage usersConfig,

paraspace-core/contracts/protocol/tokenization/libraries/MintableERC721Logic.sol#L24-26

24:        mapping(uint256 => address) owners;
25:        // Mapping from owner to list of owned token IDs
26:        mapping(address => mapping(uint256 => uint256)) ownedTokens;

paraspace-core/contracts/protocol/tokenization/libraries/MintableERC721Logic.sol#L32-34

32:        mapping(uint256 => uint256) allTokensIndex;
33:        // Map of users address and their state data (userAddress => userStateData)
34:        mapping(address => UserState) userState;

paraspace-core/contracts/protocol/tokenization/libraries/MintableERC721Logic.sol#L36-40

36:        mapping(uint256 => address) tokenApprovals;
37:        // Mapping from owner to operator approvals
38:        mapping(address => mapping(address => bool)) operatorApprovals;
39:        // Map of allowances (delegator => delegatee => allowanceAmount)
40:        mapping(address => mapping(address => uint256)) allowances;

paraspace-core/contracts/protocol/tokenization/libraries/ApeStakingLogic.sol#L205-206

205:            mapping(address => UserState) storage userState,
206:            mapping(address => mapping(uint256 => uint256)) storage ownedTokens,

[G-004] Using calldata instead of memory for read-only arguments in external functions saves gas

Impact

When a function with a memory array is called externally, the abi.decode() step has to use a for-loop to copy each index of the calldata to the memory index. Each iteration of this for-loop costs at least 60 gas (i.e. 60 * <mem_array>.length). Using calldata directly, obliviates the need for such a loop in the contract code and runtime execution. Note that even if an interface defines a function as having memory arguments, it's still valid for implementation contracs to use calldata arguments instead. If the array is passed to an internal function which passes the array to another internal function where the array is modified and therefore memory is used in the external call, it's still more gass-efficient to use calldata when the external function uses modifiers, since the modifiers may prevent the internal functions from being called. Structs have the same overhead as an array of length one.

Findings

Total:8

paraspace-core/contracts/misc/NFTFloorOracle.sol#L97-100

97:    function initialize(
98:            address _admin,
99:            address[] memory _feeders,
100:            address[] memory _assets

paraspace-core/contracts/protocol/tokenization/NTokenMAYC.sol#L97-98

97:    function withdrawBAKC(
98:            ApeCoinStaking.PairNftWithAmount[] memory _nftPairs,

paraspace-core/contracts/protocol/tokenization/NTokenBAYC.sol#L97-98

97:    function withdrawBAKC(
98:            ApeCoinStaking.PairNftWithAmount[] memory _nftPairs,

paraspace-core/contracts/protocol/tokenization/libraries/ApeStakingLogic.sol#L38-41

38:    function withdrawBAKC(
39:            ApeCoinStaking _apeCoinStaking,
40:            uint256 poolId,
41:            ApeCoinStaking.PairNftWithAmount[] memory _nftPairs,

paraspace-core/contracts/protocol/tokenization/libraries/ApeStakingLogic.sol#L232-235

232:    function withdrawBAKC(
233:            ApeCoinStaking _apeCoinStaking,
234:            uint256 poolId,
235:            ApeCoinStaking.PairNftWithAmount[] memory _nftPairs,

paraspace-core/contracts/protocol/pool/PoolApeStaking.sol#L60-62

60:    function withdrawBAKC(
61:            address nftAsset,
62:            ApeCoinStaking.PairNftWithAmount[] memory _nftPairs

paraspace-core/contracts/protocol/pool/PoolApeStaking.sol#L120-122

120:    function withdrawBAKC(
121:            address nftAsset,
122:            ApeCoinStaking.PairNftWithAmount[] memory _nftPairs

paraspace-core/contracts/protocol/pool/PoolApeStaking.sol#L188-190

188:    function withdrawBAKC(
189:            address nftAsset,
190:            ApeCoinStaking.PairNftWithAmount[] memory _nftPairs

[G-005] internal functions only called once can be inlined to save gas

Impact

Not inlining costs 20 to 40 gas because of two extra JUMP instructions and additional stack operations needed for function calls.

Findings

Total:7

paraspace-core/contracts/misc/NFTFloorOracle.sol#L265

265:    function _whenNotPaused(address _asset) internal view {

paraspace-core/contracts/misc/NFTFloorOracle.sol#L388

388:    function _addRawValue(address _asset, uint256 _twap) internal {

paraspace-core/contracts/misc/ParaSpaceOracle.sol#L218

218:    function _onlyAssetListingOrPoolAdmins() internal view {

paraspace-core/contracts/protocol/libraries/logic/MarketplaceLogic.sol#L552

552:    function _checkAllowance(address token, address operator) internal {

paraspace-core/contracts/protocol/tokenization/NTokenUniswapV3.sol#L144

144:    function _safeTransferETH(address to, uint256 value) internal {

paraspace-core/contracts/protocol/pool/PoolParameters.sol#L69

69:    function _onlyPoolConfigurator() internal view virtual {

paraspace-core/contracts/protocol/pool/PoolParameters.sol#L76

76:    function _onlyPoolAdmin() internal view virtual {

[G-006] Replace modifier with function

Impact

Modifiers make code more elegant, but cost more than normal functions.

Findings

Total:5

paraspace-core/contracts/misc/ParaSpaceOracle.sol#L34

34:    modifier onlyAssetListingOrPoolAdmins() {

paraspace-core/contracts/protocol/tokenization/base/MintableIncentivizedERC721.sol#L45

45:    modifier onlyPoolAdmin() {

paraspace-core/contracts/protocol/tokenization/base/MintableIncentivizedERC721.sol#L59

59:    modifier onlyPool() {

paraspace-core/contracts/protocol/pool/PoolParameters.sol#L56

56:    modifier onlyPoolConfigurator() {

paraspace-core/contracts/protocol/pool/PoolParameters.sol#L64

64:    modifier onlyPoolAdmin() {

[G-007] Use elementary types or a user-defined type instead of a struct that has only one member

Impact

Use elementary types or a user-defined type instead of a struct that has only one member.

Findings

Total:1

paraspace-core/contracts/protocol/tokenization/libraries/ApeStakingLogic.sol#L26-28

26:        struct APEStakingParameter {
27:            uint256 unstakeIncentive;
28:        }

[G-008] Use named returns for local variables where it is possible.

Impact

It is not necessary to have both a named return and a return statement.

Findings

Total:6

paraspace-core/contracts/misc/UniswapV3OracleWrapper.sol#L203-L215

    function getTokensPricesSum(uint256[] calldata tokenIds)
        external
        view
        returns (uint256)
    {
        uint256 sum = 0;

        for (uint256 index = 0; index < tokenIds.length; index++) {
            sum += getTokenPrice(tokenIds[index]);
        }

        return sum;
    }

paraspace-core/contracts/misc/ParaSpaceOracle.sol#L115-L136

    function getAssetPrice(address asset)
        public
        view
        override
        returns (uint256)
    {
        if (asset == BASE_CURRENCY) {
            return BASE_CURRENCY_UNIT;
        }

        uint256 price = 0;
        IEACAggregatorProxy source = IEACAggregatorProxy(assetsSources[asset]);
        if (address(source) != address(0)) {
            price = uint256(source.latestAnswer());
        }
        if (price == 0 && address(_fallbackOracle) != address(0)) {
            price = _fallbackOracle.getAssetPrice(asset);
        }

        require(price != 0, Errors.ORACLE_PRICE_NOT_READY);
        return price;
    }

paraspace-core/contracts/protocol/libraries/logic/GenericLogic.sol#L305-L321

    function calculateAvailableBorrows(
        uint256 totalCollateralInBaseCurrency,
        uint256 totalDebtInBaseCurrency,
        uint256 ltv
    ) internal pure returns (uint256) {
        uint256 availableBorrowsInBaseCurrency = totalCollateralInBaseCurrency
            .percentMul(ltv);

        if (availableBorrowsInBaseCurrency < totalDebtInBaseCurrency) {
            return 0;
        }

        availableBorrowsInBaseCurrency =
            availableBorrowsInBaseCurrency -
            totalDebtInBaseCurrency;
        return availableBorrowsInBaseCurrency;
    }

paraspace-core/contracts/protocol/libraries/logic/SupplyLogic.sol#L270-L333

    function executeWithdraw(
        mapping(address => DataTypes.ReserveData) storage reservesData,
        mapping(uint256 => address) storage reservesList,
        DataTypes.UserConfigurationMap storage userConfig,
        DataTypes.ExecuteWithdrawParams memory params
    ) external returns (uint256) {
        ...
        uint256 amountToWithdraw = params.amount;
        ...
        return amountToWithdraw;
    }

paraspace-core/contracts/protocol/libraries/logic/SupplyLogic.sol#L353-L394

    function executeWithdrawERC721(
        mapping(address => DataTypes.ReserveData) storage reservesData,
        mapping(uint256 => address) storage reservesList,
        DataTypes.UserConfigurationMap storage userConfig,
        DataTypes.ExecuteWithdrawERC721Params memory params
    ) external returns (uint256) {
        ...
        uint256 amountToWithdraw = params.tokenIds.length;
        ...
        return amountToWithdraw;
    }

paraspace-core/contracts/protocol/libraries/logic/BorrowLogic.sol#L127-L211

    function executeRepay(
        mapping(address => DataTypes.ReserveData) storage reservesData,
        DataTypes.UserConfigurationMap storage userConfig,
        DataTypes.ExecuteRepayParams memory params
    ) external returns (uint256) {
        ...
        uint256 paybackAmount = variableDebt;
        ...
        return paybackAmount;
    }

#0 - c4-judge

2023-01-25T15:50:11Z

dmvt marked the issue as grade-b

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter