Holograph contest - delfin454000's results

Omnichain protocol for deploying, minting, & bridging NFTs between blockchains.

General Information

Platform: Code4rena

Start Date: 18/10/2022

Pot Size: $75,000 USDC

Total HM: 27

Participants: 144

Period: 7 days

Judge: gzeon

Total Solo HM: 13

Id: 170

League: ETH

Holograph

Findings Distribution

Researcher Performance

Rank: 41/144

Findings: 2

Award: $82.02

QA:
grade-b
Gas:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

QA Report - low risk

Unused/empty receive() or fallback() function

Although leaving the receive() below without a revert might save gas (as @dev states), it could result in a loss of funds for a user who sends Ether to the contract (having no way to get anything back)

HolographOperator.sol: L1207-1209

   * @dev Purposefully left empty to ensure ether transfers use least amount of gas possible
   */
  receive() external payable {}

Again, leaving the receive() below without a revert could result in a loss of funds for a user who sends Ether to the contract.

Holographer.sol: L221-223

   * @dev Purposefully left empty, to prevent running out of gas errors when receiving native token payments.
   */
  receive() external payable {}

Similarly for the following:

HolographERC721.sol: L959-962

HolographERC20.sol: L249-251

ERC721H.sol: L209-212

ERC20H.sol: L210-212



QA Report: non-critical

NatSpec does not match function

NatSpec statements (including @dev @param) do not relate directly to the function below them (a function which may be incomplete).

HolographOperator.sol: L656-669

   * @dev Will provide exact costs on protocol and message side, combine the two to get total
   * @dev @param toChain holograph chain id of destination chain for payload
   * @dev @param gasLimit amount of gas to provide for executing payload on destination chain
   * @dev @param gasPrice maximum amount to pay for gas price, can be set to 0 and will be chose automatically
   * @dev @param crossChainPayload the entire packet being sent cross-chain
   * @return hlgFee the amount (in wei) of native gas token that will cost for finalizing job on destiantion chain
   * @return msgFee the amount (in wei) of native gas token that will cost for sending message to destiantion chain
   */
  function getMessageFee(
    uint32,
    uint256,
    uint256,
    bytes calldata
  ) external view returns (uint256, uint256) {


Missing NatSpec @return statements

The functions in this section have complete NatSpec, apart from missing @return statements. Below is one example:

HolographBridge.sol: L158-162

   * @notice Used internally to initialize the contract instead of through a constructor
   * @dev This function is called by the deployer/factory when creating a contract
   * @param initPayload abi encoded payload to use for contract initilaization
   */
  function init(bytes memory initPayload) external override returns (bytes4) {

Similarly for the following functions:

HolographBridge.sol: L286-301

HolographBridge.sol: L439-442

HolographBridge.sol: L459-462

HolographBridge.sol: L479-482

HolographBridge.sol: L489-492

HolographBridge.sol: L509-512

HolographBridge.sol: L529-531

HolographBridge.sol: L538-540

HolographBridge.sol: L547-549

HolographBridge.sol: L557-559

HolographBridge.sol: L566-568

Multiple instances of NatSpec missing @return statements only also occur in the remaining contracts


Missing @param statements

The functions in this section are missing @param statements (as well as @return statements in a few cases). Below is one example:

HolographOperator.sol: L442-445

   * @dev Purposefully made to be external so that Operator can call it during executeJob function
   *      Check the executeJob function to understand it's implementation
   */
  function nonRevertingBridgeCall(address msgSender, bytes calldata payload) external payable {

Missing: @param msgSender and @param payload

Similarly for the following:

HolographOperator.sol: L481-484 Missing: @param bridgeInRequestPayload

HolographOperator.sol: L574-590 Missing: @param msgSender

HolographOperator.sol: L1120-1122 Missing: @param pod and @param operatorIndex

HolographOperator.sol: L1158-1160 Missing: @return and @param pod

HolographOperator.sol: L1196-1198 Missing: @return and @param contractAddress

HolographFactory.sol: L156-163 Missing: @return and @param payload

HolographFactory.sol: L173-182 Missing: @return and @param payload

HolographFactory.sol: L307-309 Missing: @return and @param contractAddress

HolographFactory.sol: L318-326 Missing: @return and @param r, @param s, @param v, @param hash and @param signer

Multiple instances also occur in LayerZeroModule.sol, PA1D.sol, HolographERC721.sol, HolographERC20.sol, ERC721H.sol, and ERC20H.sol



Typos


The same typo (initilaization) occurs in all 10 lines referenced below:

HolographBridge.sol: L160

HolographOperator.sol: L238

HolographFactory.sol: L141

LayerZeroModule.sol: L156

Holographer.sol: L145

PA1D.sol: L171

HolographERC721.sol: L236

HolographERC20.sol: L216

ERC721H.sol: L138

ERC20H.sol: L138

   * @param initPayload abi encoded payload to use for contract initilaization

Change initilaization to initialization in each case


The same typo (destiantion) occurs in all four lines referenced below:

HolographBridge.sol: L415

HolographBridge.sol: L416

HolographOperator.sol: L661

HolographOperator.sol: L662

   * @return hlgFee the amount (in wei) of native gas token that will cost for finalizing job on destiantion chain

Change destiantion to destination in each case


The same typo (accomodate) occurs in both referenced below:

HolographOperator.sol: L515

PA1D.sol: L387

       *      decrease pod size to accomodate popped operator

Change accomodate to accommodate in both casees


HolographOperator.sol: L553

       * @dev bridgeInRequest doNotRevert is purposefully set to false so a rever would happen

Change rever to revert


HolographOperator.sol: L997

   * @dev All cross-chain message requests will get forwarded to this adress

Change adress to address


HolographFactory.sol: L188

   * @param config contract deployement configurations

Change deployement to deployment


PA1D.sol: L150

   * @param recipients Address array of wallets that will receive tha royalties.

Change tha to the


PA1D.sol: L156

   * @dev Use this modifier to lock public functions that should not be accesible to non-owners.

Change accesible to accessible


PA1D.sol: L299

    // The slot hash has been precomputed for gas optimizaion

Change optimizaion to optimization


PA1D.sol: L446

   * @dev This function validates that the call is being made by an authorised wallet.

Change authorised to authorized. The American English spelling of this word is used elsewhere in Holograph/PA1D, so this version should be used throughout for consistency


PA1D.sol: L447

   * @dev Will revert entire tranaction if it fails.

Change tranaction to tranaction


PA1D.sol: L472

    require(addresses.length == bps.length, "PA1D: missmatched array lenghts");

Change missmatched to mismatched and lenghts to lengths


PA1D.sol: L477

    require(totalBp == 10000, "PA1D: bps down't equal 10000");

Change down't to don't


HolographERC721.sol: L194

   * @dev Usually utilised for supporting marketplace proxy wallets.

Change utilised to utilized. Again, this assumes the use of American Engligh throughout for consistency


HolographERC721.sol: L263

      require(success && selector == InitializableInterface.init.selector, "ERC721: coud not init PA1D");

Change coud to could


HolographERC721.sol: L319

   * @dev Defaults the the Arweave URI

Remove repeated word the


HolographERC721.sol: L543

  //     require(wallets.length == tokenIds.length, "ERC721: array length missmatch");

Change missmatch to mismatch


HolographERC721.sol: L662

   * @dev Single operator set for a specific token. Usually used for one-time very specific authorisations.

Change authorisations to authorizations, again assuming American English


HolographERC20.sol: L154

   * @dev Mapping of all the addresse's balances.

Change addresse's to addressee's, assuming this was what was meant



Open TODOs

Comments that contain TODOs or other language referring to open items should be addressed and better explained, or else modified or removed. Below are several instances:


HolographBridge.sol: L286

   * @notice Do not call this function, it will always revert

HolographOperator.sol: L275-276

   * @dev temp function, used for quicker updates/resets during development
   *      NOT PART OF FINAL CODE !!!

HolographOperator.sol: L701

        // TODO: move the bit-shifting around to have it be sequential

PA1D.sol: L619-620

  // To be quite honest, SuperRare is a closed marketplace. They're working on opening it up but looks like they want to use private smart contracts.
  // We'll just leave this here for just in case they open the flood gates.

PA1D.sol: L666

    // this information is outside of the scope of our    [line appears to be truncated]

HolographERC721.sol: L446-447

   * @dev Since it's not being used, the _data variable is commented out to avoid compiler warnings.
   * are aware of the ERC721 protocol to prevent tokens from being forever locked.


Named return variable is not used

Named return variable (here, totalPods) is never used

HolographOperator.sol: L714-719

   * @notice Get number of pods available
   * @dev This returns number of pods that have been opened via bonding
   */
  function getTotalPods() external view returns (uint256 totalPods) {
    return _operatorPods.length;
  }


Long single line comments

In theory, comments over 80 characters should wrap using multi-line comment syntax. Even if somewhat longer comments (up to 120 characters) are acceptable and a scroll bar is provided, there are cases where very long comments interfere with readability. Below are five instances of extra-long comments whose readability could be improved by wrapping, as shown:

HolographBridge.sol: L120

 * @dev The contract abstracts all the complexities of making bridge requests and uses a universal interface to bridge any type of holographable assets

Suggestion:

 * @dev The contract abstracts all the complexities of making bridge requests and 
 *   uses a universal interface to bridge any type of holographable assets.

HolographFactory.sol: L121

 * @dev The contract provides methods that allow for the creation of Holograph Protocol compliant smart contracts, that are capable of minting holographable assets

Suggestion:

 * @dev The contract provides methods that allow for the creation of Holograph Protocol 
 *   compliant smart contracts, that are capable of minting holographable assets.

Holographer.sol: L172

   * @dev Returns the block height of when the smart contract was deployed. Useful for retrieving deployment config for re-deployment on other EVM-compatible chains.

Suggestion:

   * @dev Returns the block height of when the smart contract was deployed. Useful for 
   *   retrieving deployment config for re-deployment on other EVM-compatible chains.

PA1D.sol: L151

   * @param bps Uint256 array of base points(percentages) that each wallet(specified in recipients) will receive from the royalty payouts. Make sure that all the base points add up to a total of 10000.

Suggestion:

   * @param bps Uint256 array of base points(percentages) that each wallet(specified in recipients) will receive
   *   from the royalty payouts. Make sure that all the base points add up to a total of 10000.

PA1D.sol: L619

  // To be quite honest, SuperRare is a closed marketplace. They're working on opening it up but looks like they want to use private smart contracts.

Suggestion:

  // To be quite honest, SuperRare is a closed marketplace. They're working on
  //   opening it up but looks like they want to use private smart contracts.


Missing require revert string

A require message should be included to enable users to understand the reason for failure Recommendation: Add a brief (<= 32 char) explanatory message to each of the 31 requires referenced below. Also, consider using custom error codes (which would be cheaper than revert strings).


HolographERC721.sol: L378

      require(SourceERC721().afterApprove(tokenOwner, to, tokenId));

HolographERC721.sol: L391

      require(SourceERC721().beforeBurn(wallet, tokenId));

HolographERC721.sol: L395

      require(SourceERC721().afterBurn(wallet, tokenId));

HolographERC721.sol: L460

      require(SourceERC721().beforeSafeTransfer(from, to, tokenId, data));

HolographERC721.sol: L473

      require(SourceERC721().afterSafeTransfer(from, to, tokenId, data));

HolographERC721.sol: L486

      require(SourceERC721().beforeApprovalAll(to, approved));

HolographERC721.sol: L491

      require(SourceERC721().afterApprovalAll(to, approved));

HolographERC721.sol: L624

      require(SourceERC721().beforeTransfer(from, to, tokenId, data));

HolographERC721.sol: L628

      require(SourceERC721().afterTransfer(from, to, tokenId, data));

HolographERC721.sol: L759

      require(SourceERC721().beforeOnERC721Received(_operator, _from, address(this), _tokenId, _data));

HolographERC767.sol: L759

      require(SourceERC721().afterOnERC721Received(_operator, _from, address(this), _tokenId, _data));

HolographERC20.sol: L328

      require(SourceERC20().beforeApprove(msg.sender, spender, amount));

HolographERC20.sol: L332

      require(SourceERC20().afterApprove(msg.sender, spender, amount));

HolographERC20.sol: L339

      require(SourceERC20().beforeBurn(msg.sender, amount));

HolographERC20.sol: L343

      require(SourceERC20().afterBurn(msg.sender, amount));

HolographERC20.sol: L354

      require(SourceERC20().beforeBurn(account, amount));

HolographERC20.sol: L358

      require(SourceERC20().afterBurn(account, amount));

HolographERC20.sol: L371

      require(SourceERC20().beforeApprove(msg.sender, spender, newAllowance));

HolographERC20.sol: L375

      require(SourceERC20().afterApprove(msg.sender, spender, newAllowance));

HolographERC20.sol: L430

      require(SourceERC20().beforeApprove(msg.sender, spender, newAllowance));

HolographERC20.sol: L434

      require(SourceERC20().afterApprove(msg.sender, spender, newAllowance));

HolographERC20.sol: L447

      require(SourceERC20().beforeOnERC20Received(account, sender, address(this), amount, data));

HolographERC20.sol: L455

      require(SourceERC20().afterOnERC20Received(account, sender, address(this), amount, data));

HolographERC20.sol: L484

      require(SourceERC20().beforeApprove(account, spender, amount));

HolographERC20.sol: L488

      require(SourceERC20().afterApprove(account, spender, amount));

HolographERC20.sol: L502

      require(SourceERC20().beforeSafeTransfer(msg.sender, recipient, amount, data));

HolographERC20.sol: L507

      require(SourceERC20().afterSafeTransfer(msg.sender, recipient, amount, data));

HolographERC20.sol: L536

      require(SourceERC20().beforeSafeTransfer(account, recipient, amount, data));

HolographERC20.sol: L541

      require(SourceERC20().afterSafeTransfer(account, recipient, amount, data));

HolographERC20.sol: L582

      require(SourceERC20().beforeTransfer(msg.sender, recipient, amount));

HolographERC20.sol: L586

      require(SourceERC20().afterTransfer(msg.sender, recipient, amount));

HolographERC20.sol: L606

      require(SourceERC20().beforeTransfer(account, recipient, amount));

HolographERC20.sol: L610

      require(SourceERC20().afterTransfer(account, recipient, amount));


Event is missing indexed fields

An event should use three indexed fields if there are three or more fields


PA1D.sol: L153

  event SecondarySaleFees(uint256 tokenId, address[] recipients, uint256[] bps);


Use of exponentiation

For readability, scientific notation for large multiples of ten is preferable to using exponentiation


HolographOperator.sol: L256

      _baseBondAmount = 100 * (10**18); // one single token unit * 100

Suggestion: Change 10**18 to 1e18


LayerZeroModule.sol: L274

    return (((gasPrice * (gasLimit + (gasLimit / 10))) * dstPriceRatio) / (10**10), nativeFee);

LayerZeroModule.sol: L293

    return ((gasPrice * (gasLimit + (gasLimit / 10))) * dstPriceRatio) / (10**10);

Suggestion: Change 10**10 to 1e10 in both cases



Use != 0 instead of > 0 in a require statement if variable is an unsigned integer

!= 0 should be used where possible since > 0 costs more gas


HolographOperator.sol: L350

        require(timeDifference > 0, "HOLOGRAPH: operator has time");

Change timeDifference > 0 to timeDifference != 0


HolographERC721.sol: L815

    require(tokenId > 0, "ERC721: token id cannot be zero");

Change tokenId > 0 to tokenId != 0



Avoid use of '&&' within a require function

Splitting into separate require() statements saves gas


HolographOperator.sol: L857

    require(_bondedOperators[operator] == 0 && _bondedAmounts[operator] == 0, "HOLOGRAPH: operator is bonded");

Recommendation:

    require(_bondedOperators[operator] == 0, "HOLOGRAPH: operator is bonded");
    require(_bondedAmounts[operator] == 0, "HOLOGRAPH: operator is bonded");

Holographer.sol: L166

    require(success && selector == InitializableInterface.init.selector, "initialization failed");

Recommendation:

    require(success, "initialization failed");
    require(selector == InitializableInterface.init.selector, "initialization failed");

HolographERC721.sol: L263

      require(success && selector == InitializableInterface.init.selector, "ERC721: coud not init PA1D");

Recommendation:

      require(success, "ERC721: could not init PA1D");
      require(selector == InitializableInterface.init.selector, "ERC721: could not init PA1D");

HolographERC721.sol: L464-470

      require(
        (ERC165(to).supportsInterface(ERC165.supportsInterface.selector) &&
          ERC165(to).supportsInterface(ERC721TokenReceiver.onERC721Received.selector) &&
          ERC721TokenReceiver(to).onERC721Received(address(this), from, tokenId, data) ==
          ERC721TokenReceiver.onERC721Received.selector),
        "ERC721: onERC721Received fail"
      );

Recommendation:

      require(ERC165(to).supportsInterface(ERC165.supportsInterface.selector), "ERC721: onERC721Received fail");
      require(ERC165(to).supportsInterface(ERC721TokenReceiver.onERC721Received.selector), "ERC721: onERC721Received fail");
      require(ERC721TokenReceiver(to).onERC721Received(address(this), from, tokenId, data) ==
          ERC721TokenReceiver.onERC721Received.selector), "ERC721: onERC721Received fail");


Require revert string is too long

The revert strings below can be shortened to 32 characters or fewer (as shown) to save gas (or else consider using custom error codes throughout, which could save even more gas)

PA1D.sol: L411

    require(balance > 10000, "PA1D: Not enough tokens to transfer");

Change message to PA1D: Not enough tokens to xfer

Similarly for: PA1D.sol: L435



State variables should not be initialized to their default values

Initializing uint variables to their default value of 0 (as occurs below) is unnecessary and costs gas


HolographBridge.sol: L380

    uint256 fee = 0;

Change to: uint256 fee;


HolographOperator.sol: L310-311

    uint256 gasLimit = 0;
    uint256 gasPrice = 0;

Change to: uint256 gasLimit; and uint256 gasPrice;



Array length should not be looked up during every iteration of a for loop

Since calculating the array length costs gas, it's best to read the length of the array from memory before executing the loop, as demonstrated below:


PA1D.sol: L432-442

    for (uint256 t = 0; t < tokenAddresses.length; t++) {
      erc20 = ERC20(tokenAddresses[t]);
      balance = erc20.balanceOf(address(this));
      require(balance > 10000, "PA1D: Not enough tokens to transfer");
      // uint256 sent;
      for (uint256 i = 0; i < addresses.length; i++) {
        sending = ((bps[i] * balance) / 10000);
        require(erc20.transfer(addresses[i], sending), "PA1D: Couldn't transfer token");
        // sent = sent + sending;
      }
    }

The above has an embedded for loop. The suggestion below addresses both of the loops:

    uint256 totalTokenAddressesLength = tokenAddresses.length; 
    uint256 totalAddressesLength = addresses.length; 
    for (uint256 t = 0; t < totalTokenAddressesLength; t++) {
      erc20 = ERC20(tokenAddresses[t]);
      balance = erc20.balanceOf(address(this));
      require(balance > 10000, "PA1D: Not enough tokens to transfer");
      // uint256 sent;
      for (uint256 i = 0; i < totalAddressesLength; i++) {
        sending = ((bps[i] * balance) / 10000);
        require(erc20.transfer(addresses[i], sending), "PA1D: Couldn't transfer token");
        // sent = sent + sending;
      }
    }

Similarly for the additional for loops referenced below:

PA1D.sol: L454-459

PA1D.sol: L474-476

HolographERC721.sol: L531-536

HolographERC721.sol: L547-551

HolographERC20.sol: L564-566



Use ++i instead of i++ to increase count in a for loop

i++ (or similar counters) are used throughout Holograph. It would be better to use ++i, as demonstrated below, since use of i++ costs more gas :

HolographOperator.sol: L781-783

    for (uint256 i = 0; i < length; i++) {
      operators[i] = _operatorPods[pod][index + i];
    }

Suggestion:

    for (uint256 i = 0; i < length; ++i) {
      operators[i] = _operatorPods[pod][index + i];
    }


Avoid use of default "checked" behavior in a for loop

Underflow/overflow checks are made every time i++ (or equivalent counter) is called. Such checks are unnecessary since i is already limited. I found only one instance of a for loop that used unchecked behavior: HolographOperator.sol: L871-876 Therefore, use unchecked behavior for the remaining loops, as shown below:


HolographOperator.sol: L781-783

    for (uint256 i = 0; i < length; i++) {
      operators[i] = _operatorPods[pod][index + i];
    }

Suggestion:

    for (uint256 i = 0; i < length;) {
      operators[i] = _operatorPods[pod][index + i];

      unchecked {
        ++i;
      }
    }

Note that i++ has been changed to ++i, as recommended earlier in this submission



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