Introduction
CoinFabrik was asked to audit the contracts for the KnownOrigin project. First we will provide a summary of our discoveries and then we will show the details of our findings.
Scope
The contracts audited are from the https://github.com/knownorigin/known-origin-contracts-v3.git git repository. The audit is based on the commit d592c5f4fa4e0b6fc65a1fce43e302706aedf607.
The audited files are:
/contracts/marketplace/KODAV3UpgradableGatedMarketplace.sol:
Contains the contract used to deploy gated marketplaces./contracts/marketplace/BaseUpgradableMarketplace.sol:
Contains theBaseUpgradableMarketPlace
contract. This contract can be used to make upgradable marketplaces, including several utilities that simplify its creation./contracts/minter/MintingFactoryV2.sol:
Contains theMintingFactoryV2
contract. This contract is an upgradeable glue contract that binds several contracts to facilitate the minting process. It is intended to replace the MintingFactory contract while allowing for gated marketplaces./contracts/access/IKOAccessControlsLookup.sol:
It contains the interface definition for the contract responsible for handling authorization of different roles in the system./contracts/core/IKODAV3.sol:
It contains the interface definition for the KODAV3 token./contracts/marketplace/IKODAV3Marketplace.sol
: Contains interface definitions for different kinds of marketplace contracts./contracts/core/IKODAV3Minter.sol:
Contains the interface definition for the KODA Minter contract (version 3)./contracts/collab/ICollabRoyaltiesRegistry.sol
: Contains the interface definition for the royalties registry.
The scope of the audit is limited to those files. No other files in this repository were audited. Its dependencies are assumed to work according to their documentation. Also, no tests were reviewed for this audit.
Fixes were checked on commit 3bcd94f66e5d0f6b38881fd52971c13dd08b6974
.
After that the development team fixed a bug regarding royalties distribution. We checked that fix on commit 9cd490474667bf021c7b0c1e8b3c697fc3072f3a
and no new security issues were found in the audited files.
Analyses
Without being limited to them, the audit process included the following analyses:
● Arithmetic errors
● Outdated version of Solidity compiler
● Race conditions
● Reentrancy attacks
● Misuse of block timestamps
● Denial of service attacks
● Excessive gas usage
● Missing or misused function qualifiers
● Needlessly complex code and contract interactions
● Poor or nonexistent error handling
● Insufficient validation of the input parameters
● Incorrect handling of cryptographic signatures
● Centralization and upgradeability
Summary of Findings
We found no critical or medium issues. Several minor issues were found. Also, several enhancements were proposed.
All security issues were either resolved, mitigated or acknowledged by the development team. Some enhancements were implemented by the development team.
Security Issues
Privileged Roles
These are the privileged roles that we identified on each of the audited contracts.
MintingFactoryV2
Admin
An address with the admin role can:
● Upgrade the contract.
● Set and unset the frequency override for an address.
● Set the minting period.
● Set the royalties registry.
● Set the maximum mints in a period.
The accessControls
contract controls if an address has the admin role via the hasAdminRole
() function.
Verified Artist
An address with the verified artist role can mint batches for itself using the following functions:
mintBatchEdition()
mintBatchEditionGatedOnly()
mintBatchEditionGatedAndPublic()
mintBatchEditionOnly()
Minting is governed by the restrictions set by the admin.
The accessControls
contract controls if an address has the verified artist role via the isVerifiedArtist()
function. The required cryptographic proof is forwarded to this function.
Verified Artist Proxy
An address with the verified artist proxy role can mint batches on behalf of a creator it represents using the following functions:
mintBatchEditionAsProxy()
mintBatchEditionGatedOnlyAsProxy()
mintBatchEditionGatedAndPublicAsProxy()
mintBatchEditionOnlyAsProxy()
Minting is governed by the restrictions set by the admin.
The accessControls
contract controls if an address is a verified artist proxy for a creator via the isVerifiedArtistProxy(
) function.
BaseUpgradableMarketplace
Admin
An address with the admin role can:
- Upgrade the contract.
- Change the accessControls contract used in the contract via the
updateAccessControls()
function. In order to do so, it needs to have the admin role in both the old and newaccessControls
contracts. - Transfer away ERC20 tokens owned by the contract via the
recoverERC20()
function. - Transfer ether away owned by the contract the
recoverStuckETH()
function. - Update the platform commision via the
updatePlatformPrimaryComission()
function. This value is not used in this contract but may be used in a derived contract. - Update the modulo value via the
updateModulo()
function. This value is only used in the private andnon-invoked _handleSaleFunds()
function and may also be used in a derived contract. - Update the minBidAmount via the
updateMinBidAmount()
function. This value is not used in this contract but may be used in a derived contract. - Update the
bidLockupPeriod
via theupdateBidLockupPeriod()
function. This value is only used in the private andnon-invoked _getLockupTime()
function and may also be used in a derived contract. - Update the platformAccount value via the
updatePlatformAccount()
function. This value is only used in the private and non-invoked_handleSaleFunds()
function and may also be used in a derived contract. - Pause and resume the contract via the
pause()
andunpause()
functions.
The accessControls contract controls if an address has the admin role via the hasAdminRole()
function.
Other functions in derived contracts may be allowed to an admin only via theonlyAdmin()
modifier and/or to the admin and other roles via theonlyCreatorContractOrAdmin()
modifier.
Contract and Creator
While there are no functions marked with the onlyCreatorContractOrAdmin() modifier in this contract, this modifier can be used to restrict the execution of functions to either an admin, a contract or the issuer of the edition passed as a parameter. Checking that an address has a contract or admin role is made via the accessControls.hasContractOrAdminRole() function. To check if an address corresponds to an editionId the koda.getCreatorOfEdition() function is used.
KODAV3UpgradableGatedMarketplace
This contract inherits all the roles and capabilities defined in the BaseUpgradableMarketplace
contract. No new roles are added, but the original roles get additional capabilities.
Admin
Besides the capabilities defined in BaseUpgradableMarketplace
contract, an address with the admin role can:
- Change the fundsReceiver of a sale via the
updateFundsReceiver()
function. - Change the maxEditionId of a sale via the
updateMaxEditionId()
function. - Update the creator of a sale via the
updateCreator()
function. - Override the commission for a sale via the
setKoCommissionOverrideForSale()
function.
Admin and Contract
An address with the admin and/or contract roles can:
- Create sales for any edition via the
createSale()
andcreateSaleWithPhases()
functions. - Create and remove phases of sales for any edition via the
createPhase(),
createPhases()
andremovePhase()
functions. - Pause or resume any sale via the
toggleSalePause()
function.
Creator
An address with the admin and/or contract roles can:
- Create sales for any edition whose editionId corresponds to them via the
createSale()
andcreateSaleWithPhases()
functions. - Create and remove phases of sales for any edition whose editionId corresponds to them via the
createPhase()
, createPhases() andremovePhase()
functions. - Pause or resume any sale whose editionId corresponds to them via the
toggleSalePause(
) function.
Security Issues Found
Severity Classification
Security risks are classified as follows:
- Critical: These are issues that we manage to exploit. They compromise the system seriously. They must be fixed immediately.
- Medium: These are potentially exploitable issues. Even though we did not manage to exploit them or their impact is not clear, they might represent a security risk in the near future. We suggest fixing them as soon as possible.
- Minor: These issues represent problems that are relatively small or difficult to take advantage of but can be exploited in combination with other issues.
These kinds of issues do not block deployments in production environments. They should be taken into account and be fixed when possible.
Issues Status
An issue detected by this audit can have four distinct statuses:
- Unresolved: The issue has not been resolved.
- Acknowledged: The issue remains in the code but is a result of an intentional decision.
- Resolved: Adjusted program implementation to eliminate the risk.
- Partially resolved: Adjusted program implementation to eliminate part of the risk. The other part remains in the code but is a result of an intentional decision.
- Mitigated: Implemented actions to minimize the impact or likelihood of the risk.
Critical Severity Issues
No issues found.
Medium Severity Issues
No issues found.
Minor Severity Issues
MI-01 Possible Reentrancy Issues
Location:
● contracts/minter/MintingFactoryV2.sol
While accessControls
, koda, marketplace, gatedMarketplace
and royaltiesRegistry contracts are considered trustworthy, a crafty attacker may eventually find a way to use those to trigger a reentrancy attack in the MintingFactoryV2
contract.
Recommendation
Mark all the MintingFactoryV2.mint*()
functions as non-reentrant using the nonReentrant modifier defined in the OpenZeppelin library. Reentrancy issues are not easy to spot and the checks-effects-interaction
pattern cannot be trivially applied in this contract.
Status
Acknowledged.
MI-02 Outdated Solidity Version
The solidity version declared in the audited contracts is 0.8.4. The latest version released at the time of this audit is 0.8.13.
Recommendation
Test all contracts with the latest version and change the pragma solidity statements.
Status
Acknowledged.
MI-03 Unchecked Transfer
Location:
● contracts/marketplace/BaseUpgradableMarketplace.sol:92
When the admin recovers ERC20 tokens in the BaseUpgradableMarketplace contract, the contract does not control if the transfer to the new address is made or not.
This issue is minor because:
- Only the admin can recover ERC20 tokens.
- If the check fails, the only difference is that a
AdminRecoverERC20
event is emitted when it should not be.
Recommendation
Use OpenZeppelin’s safeTransfer()
function to transfer ERC20s.
Status
Resolved. Checked on commit 3bcd94f66e5d0f6b38881fd52971c13dd08b6974.
MI-04 Possible DOS on KODAV3UpgradableGatedMarketplace.mint()
Location:
● contracts/marketplace/BaseUpgradableMarketplace.sol:149,152
When KODAV3UpgradableGatedMarketplace.mint()
is called, eventually BaseUpgradableMarketplace._handleSalesFunds()
is called several times. In this function, 2 ether transfers are made, one to the platformAddress
and the other to the fundsReceiver
of the sale.
If any of those addresses rejects the transfer, then the minting is aborted. The severity of this issue was lowered because neither the platform nor the fund receiver has clear economic incentives to do this and the DOS is transient.
Recommendation
Use the withdrawal pattern to transfer funds to those addresses. This may also result in gas savings for multiple mints, as a single transfer would be required for each address.
Status
Mitigated. The development team informed us that they don’t want to introduce the pull pattern at this point due to UX complexity. mitigation can be achieved by an admin running the KODAV3UpgradableGatedMarketplace.updateFundsReceiver()
function. The development team also informs that the funds handler is derived from the collab registry, so they can also disable minting access to the malicious rtists if found and also update the funds handler in this scenario.
Enhancements
These items do not represent a security risk. They are best practices that we suggest implementing.
Table
Details
EN-01 Time Lock Mechanism to Transfer Funds and Upgrade
Location:
● contracts/marketplace/BaseUpgradableMarketplace.sol:87-100
If an attacker takes control of an address with the admin role, it can steal all the funds belonging to a BaseUpgradableMarketplace derived contract, including https://docs.soliditylang.org/en/v0.8.13/common-patterns.html#withdrawal-from-contracts KODAV3UpgradableGatedMarketplace
. This operation may be made instantly, giving the real admins no recourse to stop the operation.
Recommendation
Make the operations to recover ERC20s and ether, and also the upgrade of the contract, time locked. This would give the possibility to prevent this attack from happening.
Status
Not implemented. The development team acknowledges this recommendation but will not implement it.
EN-02 Missing Non-Zero Checks
Location
● contracts/marketplace/BaseUpgradableMarketplace.sol:78,96,131
The BaseUpgradableMarketplace contract is missing the checks for zero addresses, which are implemented in its child contract KODAV3UpgradableGatedMarketplace.
This may lead to using an uninitialized address as target for a transfer, losing those funds.
Recommendation
Add the non-zero checks to the following function parameters:
● _platformAccount
in the BaseUpgradableMarketplace.initialize()
function.
● _recipient in the BaseUpgradableMarketplace.recoverStuckETH()
function.
● _newPlatformAccount in the BaseUpgradableMarketplace.updatePlatformAccount()
function.
Status
Implemented. Checked on commit 3bcd94f66e5d0f6b38881fd52971c13dd08b6974.
EN-03 Commissions Calculations
Location:
contracts/marketplace/BaseUpgradableMarketplace.sol:29-32,58-67, 113-117, 146-154
contracts/marketplace/KODAV3UpgradableGatedMarketplace.sol:70,102-105, 306-311, 396-401
There are several things that can be improved in the commissions calculations:
- Decouple modulo for each commision. Now if the admin changes the module then all the commissions would be altered. It would be better for each commission to have its own modulo.
- The value for
koCommissionOverrideForSale[_saleId]
.koCommission (set inKODAV3UpgradableGatedMarketplace.sol)
and platformPrimaryCommission (set inBaseUpgradableMarketplace.sol
) should be less than its corresponding modulo. - Given that the current supply of wei is about 1.2e26 , the multiplication in 2 line 147 of
BaseUpgradableMarketplace.sol
would not overflow if written as msg.value * _platformCommission / modulo, given that _platform commission is less than 10 50. Checking that is less than 10 40 would ensure that it would not overflow for millennia. And by multiplying before dividing the calculation of the commission would be more precise. If other lockchains are added (different from ethereum), these numbers should be revised accordingly to avoid overflows.
Status
Partially implemented. Hereunder there is the split of this recommendation
implementation:
- Not implemented.
- Not implemented.
- Implemented. Checked on commit
3bcd94f66e5d0f6b38881fd52971c13dd08b6974.
Other Considerations
The considerations stated in this section are not right or wrong. We do not suggest any action to fix them. But we consider that they may be of interest for other stakeholders of the project, including users of the audited contracts, owners or project investors.
Trusted Contracts
When analyzing the MintingFactoryV2 contract we assumed that the accessControls, koda, marketplace, gatedMarketplace and royaltiesRegistry
contracts are trustworthy. Those contracts are set in the initialization phase, which is invoked during the deployment of the contract.
When analyzing the KODAV3UpgradableGatedMarketplace
contract and its parent, BaseUpgradableMarketplace, we assumed that the accessControls and koda contracts are trustworthy. Those contracts are set in the initialization phase, which is invoked during the deployment of the contract. The accessControls contract canalso be changed by an admin, using the BaseUpgradableMarketplace.updateAccessControls()
function.
Centralization and Upgrades
The contracts MintingFactoryV2,
KODAV3UpgradableGatedMarketplace
and BaseUpgradableMarketplace are quite centralized. In particular, an address with the admin role may trigger an upgrade, so being able to run any code. An administrator may also transfer any ERC20 or ether belonging to a BaseUpgradableMarketplace derived contract, including KODAV3UpgradableGatedMarketplace,
to any arbitrary address.
In the KODAV3UpgradableGatedMarketplace
contract, addresses with the contract role also have significant capabilities to interfere with any sale.
Changelog
● 2022-04-12 – Initial report based on commit d592c5f4fa4e0b6fc65a1fce43e302706aedf607.
● 2022-04-18 – Check fixes on commit 3bcd94f66e5d0f6b38881fd52971c13dd08b6974.
● 2022-04-28 – Check bug fix on commit 9cd490474667bf021c7b0c1e8b3c697fc3072f3a
.
Disclaimer: This audit report is not a security warranty, investment advice, or an approval of the KnownOrigin project since CoinFabrik has not reviewed its platform. Moreover, it does not provide a smart contract code faultlessness guarantee.