Kolor Audit Report

Copyright © 2022 by Verilog Solutions. All rights reserved.

August 19, 2022

by Verilog Solutions

This report presents our engineering engagement with the Kolor dev team on their metaverse Web3 application – Kolor’s dApp.

Project NameKolor
Repository Linkhttps://github.com/yieniggu/kolor-contracts
Commit Hash496ac663b37df81b3ea590d72f381d845768ed24
Language Solidity
ChainCelo

About Verilog Solutions

Founded by a group of cryptography researchers and smart contract engineers in North America, Verilog Solutions elevates the security standards for Web3 ecosystems by being a full-stack Web3 security firm covering smart contract security, consensus security, and operational security for Web3 projects.

Verilog Solutions team works closely with major ecosystems and Web3 projects and applies a quality above quantity approach with a continuous security model. Verilog Solutions onboards the best and most innovative projects and provides the best-in-class advisory services on security needs, including on-chain and off-chain components.

Table of Contents

Service Scope

Service Stages

  1. Architecture Consultancy Service
    • Protocol Security & Design Discussion Meeting
      • As a part of the audit service, the Verilog Solutions team worked closely with the Kolor development team to discuss potential vulnerability and smart contract development best practices in a timely fashion. Verilog Solutions team was very appreciative of establishing an efficient and effective communication channel with the Kolor team, as new findings were exchanged promptly and fixes were deployed quickly, during the preliminary report stage.
  1. Smart Contract Auditing Service
    • The Verilog Solutions team analyzed the entire project using a detailed-oriented approach to capture the fundamental logic and suggested improvements to the existing code. Details can be found under Findings & Improvement Suggestions.

Methodology

  • Code Assessment
    • We evaluate the overall quality of the code and comments as well as the architecture of the repository.
    • We help the project dev team improve the overall quality of the repository by providing suggestions on refactorization to follow the best practice of Web3 software engineering.
  • Code Logic Analysis
    • We dive into the data structures and algorithms in the repository and provide suggestions to improve the data structures and algorithms for the lower time and space complexities.
    • We analyze the hierarchy among multiple modules and the relations among the source code files in the repository and provide suggestions to improve the code architecture with better readability, reusability, and extensibility.
  • Access Control Analysis
    • We perform a comprehensive assessment of the special roles of the project, including their authorities and privileges.
    • We provide suggestions regarding the best practice of privilege role management according to the standard operating procedures (SOP).
  • Off-Chain Components Analysis
    • We analyze the off-chain modules that are interacting with the on-chain functionalities and provide suggestions according to the SOP.
    • We conduct a comprehensive investigation for potential risks and hacks that may happen on the off-chain components and provide suggestions for patches.

Audit Scope

Project Summary

Kolor’s protocol is a metaverse project that includes tokens, NFTs, and a marketplace system. Regular users receive land tokens that represent investments in land NFTs minted and customized by the Kolor team with species and geospatial points. On the other hand, companies are able to add value to those NFTs by offsetting emissions on them. All the interactions happen on Kolor’s platform.

Kolor’s dApp System Architecture

Kolor’s dApp consists of three smart contracts with the following main logic blocks:

KolorLandNFT.sol

contains the main logic of minting NFTs and transferring them to the marketplace.

This contract has the following main functions:

safeMint(): It mints NFT and updates relevant information about it.

updateLandState(): It updates the land NFT state.

addBuyer(): Kolor marketplace can add a new buyer to the land NFT.

safeTransferToMarketplace(): It transfers the NFT to the marketplace and marks it as published for buyers to offset emissions.

setSpecies(): If the species haven’t been set, it sets all species of a certain land NFT.

addSpecies(): It adds species to the input land NFT.

updateSpecies(): It updates the species info of the input land NFT.

setPoints(): If the points haven’t been set, sets geospatial points of the input land NFT.

addPoint(): It adds the geospatial point to the input land NFT.

updatePoint(): It updates geospatial point info of the input land NFT.

offsetEmissions(): Kolor marketplace can call this function to update the sold TCO2 of land NFT.

KolorLandToken.sol

contains the logic for minting tokens and adding investments.

It contains the main logic of minting land tokens and using them to add investments to land NFTs, 1 land token is associated with 1 square meter of the NFT. This contract has the following main functions:

setLandTokenInfo(): It sets land token info.

addNewTokens(): It mints land tokens.

newInvestment(): It updates the number of holders, sets approval for dev or another operator, and updates balances and investments.

safeTransferFrom(): It transfers tokens from one user to another.

KolorMarketplace.sol

contains the logic for offsetting emissions on the minted NFTs.

Once the NFT is transferred to the marketplace, this contract contains the logic for offsetting emissions on them. This contract has the following main functions:

removeLand(): It updates the state of input land to paused.

offset emissions(): If land NFT is published and has enough TCO2 to offset, it adds buyers to the land and creates information about the offset.

addOffsetsEmissionsOfBuyer(): It updates registry emissions per buyer.

addOffsetsEmmisionsOfLand(): It updates registry emissions per land.

Use Case Scenario

The Kolor’s dApp is a metaverse platform that allows users to invest in NFTs that represent different lands in the real world. Each land is defined by the species and geospatial points that shape them. Once the land is created, it will start emitting carbon dioxide which can be offset as long as the land has been transferred to the marketplace. Value and excitement are added once big companies offset emissions on the land.

Findings & Improvement Suggestions

SeverityTotalAcknowledgedResolved
High101
Medium927
Low211
Informational1183

High

  1. Addresses issue
    SeverityHigh
    SourceKolorLandNFT.sol#L120, L127;
    StatusResolved in commit 7c091960656518c20169e3493ec476f44938e93b;
    • Description

      Mappings in safeMint() function don’t get updated with the correct address. This causes several critical functions to not work properly. There are two important address type variables passed into function KolorLandNFT.safeMint(), which are to and landOwner. All land NFTs are minted to the to address, which is an address controlled by the Kolor team. The function only records and updates the NFT owner (i.e., landOwner) in the internal mappings mintedNFTSInfo, ownedLand, _totalLandOwned. However, the address to is used when updating some of those mappings where landOwner should be used. Setting this incorrectly causes ownedLands and _totalLandsOwned to update the wrong address. This affects functions like totalLandOwnedOf(), landOfOwnerByIndex(), updateLandOwner(), etc.

    • Exploit Scenario
      1. Kolor team mints NFT and set Alice as the landOwner;
      1. Since mapping are not correctly updated, landOwner mappings get assigned to Kolor team;
      1. Several functions cannot work correctly because of the wrong information in the mapping.
    • Recommendations

      Update mappings using correct addresses. Consider the following lines:

      // set the tokenId to current landowner collection index
      ownedLands[to][_landsOwned] = currentTokenId;
      // increase total lands owned by address
      _totalLandOwned[to]++;

      Change them to this:

      // set the tokenId to current landowner collection index
      ownedLands[landOwner][_landsOwned] = currentTokenId;
      // increase total lands owned by address
      _totalLandOwned[landOwner]++;

Medium

  1. Inconsistency in business logic and actual implementation
    SeverityMedium
    SourceKolorMarketplace.sol#L54; KolorLandNFT.sol#L187;
    StatusResolved in commit 4ce7e209dcdb3842b545aa14585359a727479abf;
    • Description

      Functions removeLand() and updateLandState() remove NFTs from the marketplace by changing the published state. However, there’s no logic to returning the emissions offset to buyers or tokens to investors and returning NFT to the sender. These functions update the state of the land token. So, it does not allow any more buyers to offset emissions on the land NFTs. Nevertheless, if a buyer had already offset emissions, they would lose all the TCO2 since there’s no logic to return the already offset emissions. Additionally, there’s no restriction for investors to add investments but they should get their tokens back when the state of the NFT is changed. Buyers and investors cannot recover their emissions and tokens respectively when the state is switched, which prevents them from using those emissions and tokens on other NFTs.

    • Exploit Scenario

      Buyer offsets emissions on land NFT that is already in the marketplace and the user already has investment on it, and then the NFT is removed from it by updating its state. Now, the buyer is not able to recover those emissions and the user is not able to recover tokens unless the state is once again changed to published.

    • Recommendations

      Add logic to return the emissions to buyers and tokens to investors when the land NFT is removed from the marketplace and returned to the initial sender.

  1. Inconsistency in the function name and actual implementation
    SeverityMedium
    SourceKolorLandNFT.sol#L282;
    StatusResolved in commit 4ce7e209dcdb3842b545aa14585359a727479abf;
    • Description

      The require statement in function landOfOwnerByIndex() uses the balanceOf() the landOwner instead of totalLandOwnedOf(). The false require statement in landOfOwnerByIndex can cause unexpected reverts. Function balanceOf() is inherited from ERC721.sol and therefore returns the count of NFT minted by a certain address. Nevertheless, when creating an NFT one can assign a different landOwner, sobalanceOf() would not be really counting the number of lands owned by a certain address which is what function landOfOwnerByIndex() tries to return for an input index. Not checking with the correct function in the require statement of function landOfOwnerByIndex() can cause a revert.

    • Exploit Scenario

      Kolor team mints an NFT and updates the landOwner to Alice, then Alice is the owner of one NFT but calling landOfOwnerByIndex() will cause a revert since the NFT wasn’t minted to her.

    • Recommendations

      Change balanceOf() to totalLandOwnedOf() in the require statement in function landOfOwnerByIndex(). Also update _totalLandOwned mapping when calling updateLandOwner().

    • Results

      Resolved in commit 4ce7e209dcdb3842b545aa14585359a727479abf.

      Mapping _totalLandOwned now gets updated when calling updateLandOwner(). Additionally, require statement for function landOfOwnerByIndex() has been fixed.

  1. Excessive access control
    SeverityMedium
    SourceGlobal
    StatusResolved in commit 4ce7e209dcdb3842b545aa14585359a727479abf;
    • Description

      The protocol has access control risks. Since the project team address has the ability to control almost all the functionality, a key leakage would have devastating effects. Most of the functions use the onlyAuthorized() modifier which means only the account address authorized can get access to the functionality. Since this is set to true for the sender in the constructor(), the authorization relies solely on one account in the beginning. A private key leakage from the authorized address will result in the loss of both control and access to the protocol.

    • Exploit Scenario

      If the team holds the key and the key got leaked, an attacker can transfer ownership of the contracts, authorize their address and unauthorize the team’s address. This will lead to the team not having access and control over the protocol anymore.

    • Recommendations

      Implement an extra admin layer to the protocol following something similar to a hot and cold wallet admin system.

      • Access control

        Most functions for the three contracts come with the onlyAuthorized() modifier that only allows authorized addresses to execute functions. This is set to true on the deployment of the contract for the sender of the transaction, however, other addresses can get authorized by calling the authorize() function on any of the three contracts. The authorize() function can only be called by the owner of the respective contract.

        Since the access control for the protocol relies very heavily on only one address, we recommend the use of an extra admin layer, so that in case the private key of the admin address gets leaked, the funds will not get drained. In this case, a cold wallet will be the admin storing the protected keys and will allow the hot wallet to perform all the functionality.

      • OpSec on the usage of hot and cold wallet

        The two-layer admin system would consist of a cold wallet, of which keys will be used really infrequently, preferably only in the case of an attack, and a hot wallet that will be the one that performs all the logic of the protocol and which keys will be used very often. The cold wallet will be able to set and revoke the hot wallet in charge of executing the functionality of the project.

        The use of this system allows for extra security throughout the entire protocol since the hot wallet is making many transactions, and it becomes really exposed to possible attacks. Therefore, having this extra layer prevents from losing control over the protocol if the keys of the hot wallet get compromised or hacked.

    • Results

      Resolved in commit 4ce7e209dcdb3842b545aa14585359a727479abf.

      owner will authorize an account to perform all the functionality of the protocol. If the key gets leaked, owner will unauthorize that account and authorize a different account.

  1. Incorrect payable modifier
    SeverityMedium
    SourceKolorMarketplace.sol#L65, L231;
    StatusResolved in commit 4ce7e209dcdb3842b545aa14585359a727479abf;
    • Description

      Function offsetEmissions() is payable, therefore the contract is able to receive CELO through that function and there’s no way to withdraw it. The payable functions in solidity are able to receive the native currency of the blockchain when being called. However, for this contract, this is not necessary since all the functionality lies in updating variables and mappings. Also, since there’s no withdraw function in the contract, there’s no way to recover sent tokens through that function. So, mistakenly sending tokens through this function will get them locked in the contract forever.

    • Exploit Scenario

      If mistakenly send CELO through that function, those tokens will be locked forever.

    • Recommendations

      Remove the payable keyword and the getBalance() function.

  1. LandToken info can be initialized twice
    SeverityMedium
    SourceKolorLandToken.sol#L90;
    StatusResolved in commit 4ce7e209dcdb3842b545aa14585359a727479abf;
    • Description

      KolorLandToken.setLandTokenInfo() initializes info for land tokens. However, this function does not check whether the land token has been set before and thus the land token info can be initialized again. Function KolorLandToken.setLandTokenInfo() sets the land token’s initial amount, sold amount, and creation date. By setting the initial amount, the amount will be added to the land token’s current amount and available amount. And the sold amount will be reset to 0. However, these are critical info that will be updated by other functions like KolorLandToken.NewInvestment(). By being able to set the land token info again, that critical Investment info for this land token can be reset or overwritten.

    • Exploit Scenario
      1. Authorized account Alice set initial land info for land token 123 by calling function KolorLandToken.setLandTokenInfo();
      1. Alice adds a new investment for investor Bob on land token 123 by calling the function KolorLandToken.NewInvestment();
      1. Alice called the function KolorLandToken.setLandTokenInfo() to reset the investment amount and sold amount on the land token.
    • Recommendations

      Check if the land token info initialized in KolorLandToken.setLandTokenInfo().

      function setLandTokenInfo(uint256 tokenId, uint256 initialAmount) external onlyAuthorized {
          require(exists(tokenId), "KolorLandToken: land must exists!");
      		require(landTokensInfo[tokenId].initialAmount == 0, "KolorLandToken: land token has initialized!");
      
          landTokensInfo[tokenId].initialAmount = initialAmount;
          landTokensInfo[tokenId].sold = 0;
          landTokensInfo[tokenId].creationDate = block.timestamp;
      
          addNewTokens(tokenId, initialAmount);
      }
  1. Authorization issue
    SeverityMedium
    SourceKolorLandToken.sol#L103;
    StatusAcknowledged.
    • Description

      Function KolorLandToken.addNewTokens() calls function KolorLandToken.mint() to mint new tokens. The function call may fail because KolorLandToken.mint() function requires only an authorized caller. When function KolorLandToken.addNewTokens() calls the function KolorLandToken.mint(), the caller of function KolorLandToken.mint() is the contract itself, address(this). Function KolorLandToken.mint() requires the caller to be an authorized user only. The function call may revert if the contract itself is not added to the authorized address list.

    • Exploit Scenario

      Contract KolorLandToken.sol hasn’t been authorized and authorized user calls addNewTokens(), transaction will revert because the internal call is done from KolorLandToken.sol which isn’t authorized.

    • Recommendations

      In the contract design, we recommend marking the function KolorLandToken.mint() be private or internal if it will only be called inside function KolorLandToken.addNewTokens().

    • Results

      Acknowledged.

      Kolor team will call the authorize() function on the contract to authorize the contract address.

  1. Land NFT token id should start from a non-zero value
    SeverityMedium
    SourceKolorLandNFT.sol#L287;
    StatusAcknowledged;
    • Description

      The default value 0 is the return result when accessing mapping KolorLandNFT.ownedLands. Its default return is 0 when the mapping is not set. To avoid confusion between token id 0 and empty value, the land NFT token id should increment from a non-zero value. The mapping KolorLandNFT.ownedLands returns the land NFT token id given owner and the owner’s land NFT index. As the mapping’s default return is zero when a value or mapping does not exist, we might confuse it with the return of the actual NFT token id.

    • Exploit Scenario
      1. Alice has an NFT, token id is 0, index 7;
      1. Bob does not own any land NFT;
      1. The mapping KolorLandNFT.ownedLands return 0 when querying with Bob’s address;
      1. The system will assume Bob owned the token id 0 NFT.
    • Recommendations

      Start Land NFT’s token id from 0.

    • Results

      Acknowledged.

      Kolor team decided to leave this part as it was.

  1. Missing updates for land owner-related mapping
    SeverityMedium
    SourceKolorLandNFT.sol#L197;
    StatusResolved in commit 7c091960656518c20169e3493ec476f44938e93b;
    • Description

      KolorLandNFT.ownedLands is not updated when updating land owners.

      Function KolorLandNFT.updateLandOwner() is used to update a land’s owner. Mapping KolorLandNFT.ownedLands which records all lands owned by a person is not updated when the function updates the land owner. The mapping KolorLandNFT.ownedLands is also used in function KolorLandNFT.landOfOwnerByIndex(). Given the land owner and the index of the land owner’s land, it will return the corresponding land token Index. If the mapping KolorLandNFT.ownedLands is not updated correctly, then false results might be returned when we query the function. Land may have been transferred to others but the function still returns the land’s previous owner.

    • Exploit Scenario
      1. Alice has one land NFT with token id 678 and the index is 100 in mapping ownedLands;
      1. The land owner has been updated to Bob;
      1. When searching the land index in Alice’s owned lands, it still returns land NFT 678.
    • Recommendations

      Update ownedLands mapping when calling updateLandOwner() function.

      When updating the land owner with the given token id and new land owner:

      1. Update the mintedNFTInfo and Iterate through the current land owner’s lands to get the correct land id IdOfLandNeedsUpdated;
      1. Update the old owner’s ownedLands:
        1. Get the last token index of the current owner by using _totalLandOwned[oldOwner] and then get the token id with the index:
          uint256 lastTokenIndex = _totalLandOwned[currentOwner] - 1; // need to check 
          uint256 tokenId = ownedLands[oldOwner][lastTokenIndex];
        1. Move the last land NFT token to the position where the land needs to be removed and update the _totalLandOwned[currentOwner]:
          ownedLands[currentOwner][IdOfLandNeedsRemove] = tokenId;
          ownedLands[currentOwner][lastTokenIndex] = 0; // assume 0 represent empty / not exist. see the finding Medium 7
          _totalLandOwned[currentOwner]--;
      1. Update the new owner’s ownedLands:
        1. Append the land NFT to the new owner and update the _totalLandOwned[oldOwner]:
          uint256 newTokenIndexOfNewOwner = _totalLandOwned[newOwner];
          ownedLands[newOwner][newTokenIndexOfNewOwner] = IdOfLandNeedsUpdated;
          _totalLandOwned[newOwner]++;
    • Results

      Resolved in commit 7c091960656518c20169e3493ec476f44938e93b.

      The updateLandOwner() function was removed. Kolor team acknowledges that this results in not being able to change the land owner after minting the NFT anymore.

  1. Should check before adding new buyers
    SeverityMedium
    SourceKolorLandNFT.sol#L214;
    StatusResolved in commit 7c091960656518c20169e3493ec476f44938e93b;
    • Description

      Function addBuyer() doesn’t check whether the buyer has already been added or not. The missing check for this might cause totalBuyers[tokenId] to be bigger than it should be as it is incremented even if a buyer already exists:

      function addBuyer(uint256 tokenId, address newBuyer) public override onlyMarketplace notBurned(tokenId){
          buyers[tokenId][newBuyer] = true;
          totalBuyers[tokenId]++;		
      }

      Function addBuyer() adds input address to mapping totalBuyers that counts the number of addresses that have offset emissions on certain land. However, this function doesn’t check whether the input address has already been added or not. This can result in counting the same address multiple times.

    • Exploit Scenario
      1. Kolor team adds a buyer to offset emission on a land NFT;
      1. Kolor team adds the same buyer to offset emissions on that same NFT;
      1. Mapping totalBuyers buyer is 2 instead of 1.
    • Recommendations

      Change it to this:

      function addBuyer(uint256 tokenId, address newBuyer) public override onlyMarketplace notBurned(tokenId){
          if(!buyers[tokenId][newBuyer]) {
      		    buyers[tokenId][newBuyer] = true;
              totalBuyers[tokenId]++;
          }
      }

Low

  1. Lack of updates
    SeverityLow
    SourceKolorLandToken.sol#L199;
    StatusAcknowledged;
    • Description

      Function safetransferFrom() doesn’t update mapping variables totalInvestmentsByAddress, totalInvestmentsByLand, and totalInvestments when called directly by users.

    • Exploit Scenario
      1. Alice gets investment for a land NFT;
      1. Alice calls the safeTransferFrom() to send part of the tokens that represent the investment to Bob;
      1. Since mappings weren’t updated, functions that depend on those mappings will not consider the new investment. For example, calling totalInvestmentsOfLand() will not consider Bob’s investment.
    • Recommendations

      Update mapping variables in safetransferFrom() function.

    • Results

      Acknowledged.

      Kolor team expects users not to call the safeTransferFrom(). If they call it, they will consider only the initial investment. No view function for mapping KolorLandNFT.landIndex.

  1. Mapping visibility
    SeverityLow
    SourceKolorLandToken.sol#L199;
    StatusResolved in commit 7c091960656518c20169e3493ec476f44938e93b;
    • Description

      Mapping landIndex gets updated only at KolorLandNFT.safeMint() and it doesn’t have public getter functions. This makes it difficult to get values directly from it.

    • Exploit Scenario

      N/A.

    • Recommendations

      Change the mapping’s visibility from private to public or create a public getter function for the mapping.

Informational

  1. Incorrect function visibility
    SeverityInformational
    SourceKolorMarketplace.sol#L239;
    StatusAcknowledged.
    • Description

      Function onERC721Received() visibility can be restricted to pure since it is only returning the selector of the function. This allows for gas optimization.

    • Exploit Scenario

      N/A.

    • Recommendations

      Change visibility of the function to pure.

    • Results

      Acknowledged.

  1. Unused functions
    SeverityInformational
    SourceKolorMarketplace.sol#24; KolorLandNFT.sol#L167;
    StatusResolved in commit 4ce7e209dcdb3842b545aa14585359a727479abf;
    • Description

      Internal function isLandOwner() in KolorLandNFT.sol is never used. Private mapping lockStartTime is defined but never used.

    • Exploit Scenario

      N/A.

    • Recommendations

      Remove that function or change visibility.

  1. Functions with the same names
    SeverityInformational
    SourceKolorMarketplace.sol#L158, L162, L169, L188, L208, L220;
    StatusResolved in commit 4ce7e209dcdb3842b545aa14585359a727479abf;
    • Description

      There are functions with the same but with different parameters. It’s good practice to have explicit and explanatory names for each function. Additionally, having the same name for two functions that have the same number of parameters can be confusing to other developers.

    • Exploit Scenario

      N/A.

    • Recommendations

      Change the names of functions so that names are different.

  1. Missing comments
    SeverityInformational
    SourceKolorLandToken.sol#L103, L125; KolorLandNFT.sol#L447;
    StatusAcknowledged.
    • Description

      There are no comments in some functions. The use of natspec is recommended for writing comments and it is important for crucial functions so that other developers can easily understand the use of these functions and how to interact with them.

    • Exploit Scenario

      N/A.

    • Recommendations

      Add comments to the functions specified in the source.

    • Results

      Acknowledged.

  1. Unused parameters
    SeverityInformational
    SourceKolorMarketplace.sol#L239;
    StatusAcknowledged.
    • Description

      Input parameters address operator, address from, uint256 tokenId and bytes calldata data are not used on function onERC721Received().

    • Exploit Scenario

      N/A.

    • Recommendations

      Comment unused parameters.

    • Results

      Acknowledged.

  1. Naming issue
    SeverityInformational
    SourceKolorMarketplace.sol#L85, L97; KolorLandToken.sol#L94, L107, L119, L131;
    StatusAcknowledged.
    • Description

      Typo in comment for require statements and in developer comment.

      require(erc721.ownerOf(tokenId) != address(0), "Token doesn't exists!");
      /** @dev Adds a new offset structure to a account to represent
    • Exploit Scenario

      N/A.

    • Recommendations

      Change the word “exists” to “exist” and “a account” to “an account”.

    • Results

      Acknowledged.

  1. Floating solidity pragma version
    SeverityInformational
    SourceGlobal
    StatusAcknowledged.
    • Description

      Current smart contracts use ^0.8.0. And compilers within versions≥ 0.8.0 and <0.9.0 can be used to compile those contracts. Therefore, the contract may be deployed with a newer or latest compiler version which generally has higher risks of undiscovered bugs. It is a good practice to fix the solidity pragma version if the contract is not designed as a package or library that will be used by other projects or developers

    • Exploit Scenario

      N/A.

    • Recommendations

      Use the fixed solidity pragma version.

    • Results

      Acknowledged.

  1. Update condition in require statement
    SeverityInformational
    SourceKolorLandToken.sol#L133;
    StatusResolved in commit 4ce7e209dcdb3842b545aa14585359a727479abf;
    • Description

      Update the require condition to receive proper revert messages. In the following require statement, the transaction will not revert with the desired message “KolorLandToken: exceeds max amount” when availableTokensOf(tokenId) is smaller than the amount. It will only revert with the subtraction underflow error message.

      require(availableTokensOf(tokenId) - amount >= 0,
          "KolorLandToken: exceeds max amount"
      );
    • Exploit Scenario

      N/A.

    • Recommendations

      Change the require statement to:

      require(availableTokensOf(tokenId) >= amount,
          "KolorLandToken: exceeds max amount"
      );
  1. Follow check-effects-interactions
    SeverityInformational
    SourceKolorLandToken.sol#L125;
    StatusAcknowledged.
    • Description

      Check check-effects-interactions pattern for functions like function with external function calls, such as KolorLandToken.newInvestment() to reduce the attacking surface. Check states first, then apply changes to related states and call the external function in the final step. In this way, attacking surfaces for the external function is reduced. For normal external functions without before-call hooks, there is no surface for them to reenter the function because there is no attacking interface after the external function call.

    • Exploit Scenario

      N/A.

    • Recommendations

      Follow check-effects-interactions practices.

    • Results

      Acknowledged.

  1. Incorrect comment/missing event
    SeverityInformational
    SourceKolorLandToken.sol#L383, L392;
    StatusAcknowledged.
    • Description

      Function comment says that an event is emitted when calling setApprovalForAll(), but there are no events emitted in the contract and specifically in this function.

      * @dev Approve `operator` to operate on all of `owner` tokens
      *
      * Emits a {ApprovalForAll} event.
      */
      function setApprovalForAll(
          address owner,
          address operator,
          bool approved
      ) internal virtual {
          require(owner != operator, "ERC1155: setting approval status for self");
          operatorApprovals[owner][operator] = approved;
          //emit ApprovalForAll(owner, operator, approved);
      }
    • Exploit Scenario

      N/A.

    • Recommendations

      Event emission is recommended as it can help monitor the function related to critical operations.

    • Results

      Acknowledged.

  1. Redundant code
    SeverityInformational
    SourceKolorMarketplace.sol#L70, L71;
    StatusAcknowledged;
    • Description

      Contract KolorLandNFT.sol, interface IKolorLandNFT, and IERC721 are used to make calls at the same time. This is unnecessary since the contract has already all the functionality and all the calls could be made by directly importing it. Even better would be if the interface had access to all the functionality and this was the one getting used to making the calls.

      IKolorLandNFT ilandInterface = IKolorLandNFT(KolorLandNFTAddress);
      KolorLandNFT kolorLand = KolorLandNFT(KolorLandNFTAddress);
      IERC721 erc721 = IERC721(KolorLandNFTAddress);
    • Exploit Scenario

      N/A.

    • Recommendations

      Make the interface IKolorLandNFT inherits IERC721 and add other needed functions to interface IKolorLandNFT. In this way, only IKolorLandNFT is needed. No need to use all three of them.

    • Results

      Acknowledged.

Access Control Analysis

The owner of the contracts is the deployer who can set and unset authorized addresses. Most functions for the three contracts come with the onlyAuthorized() modifier that only allows authorized addresses to execute functions. The main access control for each of the contracts is the following:

KolorLandNFT.sol

  • Allows to mint NFTs and customize them with the species and geospatial points.
  • Allows to transfer the NFTs to the marketplace.

KolorLandToken.sol

  • Allows to mint tokens that are associated one to one with the size of the land NFT.
  • Allows to add investments by setting users with balances for the respective tokens.

KolorMarketplace.sol

  • Allows to offset emissions on land NFTs once they’re transferred to the marketplace.
  • Allows to set the state of the NFT to removed.

Appendix I: Severity Categories

SeverityDescription
HighIssues that are highly exploitable security vulnerabilities. It may cause direct loss of funds / permanent freezing of funds. All high severity issues should be resolved.
MediumIssues that are only exploitable under some conditions or with some privileged access to the system. Users’ yields/rewards/information is at risk. All medium severity issues should be resolved unless there is a clear reason not to.
LowIssues that are low risk. Not fixing those issues will not result in the failure of the system. A fix on low severity issues is recommended but subject to the clients’ decisions.
InformationalIssues that pose no risk to the system and are related to the security best practices. Not fixing those issues will not result in the failure of the system. A fix on informational issues or adoption of those security best practices-related suggestions is recommended but subject to clients’ decision.

Appendix II: Status Categories

StatusDescription
UnresolvedThe issue is not acknowledged and not resolved.
Partially ResolvedThe issue has been partially resolved.
AcknowledgedThe Finding / Suggestion is acknowledged but not fixed / not implemented.
ResolvedThe issue has been sufficiently resolved.

Disclaimer

Verilog Solutions receives compensation from one or more clients for performing the smart contract and auditing analysis contained in these reports. The report created is solely for Clients and published with their consent. As such, the scope of our audit is limited to a review of code, and only the code we note as being within the scope of our audit detailed in this report. It is important to note that the Solidity code itself presents unique and unquantifiable risks since the Solidity language itself remains under current development and is subject to unknown risks and flaws. Our sole goal is to help reduce the attack vectors and the high level of variance associated with utilizing new and consistently changing technologies. Thus, Verilog Solutions in no way claims any guarantee of security or functionality of the technology we agree to analyze.

In addition, Verilog Solutions reports do not provide any indication of the technologies proprietors, business, business model, or legal compliance. As such, reports do not provide investment advice and should not be used to make decisions about investment or involvement with any particular project. Verilog Solutions has the right to distribute the Report through other means, including via Verilog Solutions publications and other distributions. Verilog Solutions makes the reports available to parties other than the Clients (i.e., “third parties”) – on its website in hopes that it can help the blockchain ecosystem develop technical best practices in this rapidly evolving area of innovation.