Unseen - Audit

CertiKFiles named in the report

CertiK reviewed The Unseen (Unseen - Audit) on 5 Aug 2024: 21 findings, 2 critical; 16 marked fixed, 5 open.

1 open critical finding
Open The Unseen’s security record

The original report was removed from its address (seen 15 Oct 2026).

Trustblock does not review code.

Findings21

Statuses are the security firm’s own, from its report.

SeverityOpenAcknowledgedFixed
Critical101
High201
Medium000
Low2014
CriticalUser Asset Can Be Taken Over By Another User's AuFixed

The `ProxyRegistry` contract plays an important role in the `TheGenerates` contract. Specifically, in the `MarketsPreapproved` contract (which is inherited by `TheGenerates` contract), the `isApprovedForAll()` function returns `true` if the `operator` is a proxy for the user. Subsequently, the `isAuthorized` modifier defined in the `ERC4907A` contract would pass for the user's `tokenId`, which means that the proxy can control the NFT corresponding to the `tokenId`. The proxy is recorded in the `ProxyRegistry` contract, specifically the `proxies` mapping. The intended behavior is that a user can only set its own proxy, and not any other user's proxy, so a user can control the NFT either directly or via its proxy. However, the `transferAccessTo()` function in the `ProxyRegistry` contract is capable of updating the `proxies[to]` mapping, and the access control in the `ProxyRegistry` and `AuthenticatedProxy` contracts is insufficient. This enables user Bob to change the `proxies[Alice]` to his own proxy, and subsequently takes control of any `tokenId` that Alice owns via Bob's proxy. Note that in the `AuthenticatedProxy` contract, the `transferProxyOwnership()` function enables the proxy owner to transfer the ownership to a new owner, and update the mapping in the registry. However, the `AuthenticatedProxy` contract also contains a `proxy()` function and a `proxyAssert()` function that can also call the `transferAccessTo()` function of the Registry, but does NOT change the `owner` variable. This allows Bob to retain ownership of his proxy, while updating the `proxies[Alice]` value in the `ProxyRegistry` contract. A proof of concept test case is added to the `exchange.registry.spec.ts` file to demonstrate the above. This vulnerability impacts all other contracts that utilize the registry and proxy contracts, including `TheGenerates` and `UnseenExchange`. For `TheGenerates`, a malicious user could effectively take control of another user's NFT. For `UnseenExchange`, the ability to manipulate the `proxy` address in line 356 of the `ExchangeCore` contract allows a malicious user to brick legitimate atomic match transactions.

HighInit Function Can Be Called Multiple TimesFixed

The `init` function is supposed to be called by the contract owner only one time, as the comment in line 116 indicates. However, the check in line 120 does not prevent the function from being called more than once. As a result, the owner can change the `tokenId` and add new minters at will. This represents a significant risk to the token holders, as a new minter can mint a large amount of tokens and also arbitrarily burn tokens from other addresses. ```solidity=113 /** * @notice Initialize the bridge parmas. * * This can only be called by the contract owner and one time. */ function init(bytes32 tokenId_, address tokenManager) external onlyOwner { if (tokenId_ == bytes32(0)) revert TokenIdZero(); if (tokenId_ == tokenId) revert TokenIdIsAlreadySet(); tokenId = tokenId_; _addMinter(tokenManager); } ```

Low`Executor` Used Instead of `Maker` in Order ValidFixed

The `validateOrderAuthorization()` function is intended to "validate if maker signed the order hash" (comment in line 173). However, in line 459 and 471, the `executor` address is passed in to the function instead. If the `maker` and `executor` are different addresses, this represents the inconsistency between the intended design and the actual behavior of the code.

LowEIP4906 Not Strictly FollowedFixed

According to EIP 4906 (https://eips.ethereum.org/EIPS/eip-4906), The `supportsInterface` method MUST return `true` when called with `0x49064906`. The `VestingLockup` contract inherits `IERC4906` but the `supportsInterface` method is not updated to conform to the EIP 4906 standard.

LowERC721 Tokens May Be Minted To A Contract That CaFixed

When the owner creates a new schedule either via `createSchedule()` or `createMultiSchedules()`, there is no check that the `params.recipient` can handle the received ERC721. If `params.recipient` is a smart contract that cannot handle the ERC721, the corresponding vesting schedule could be unwithdrawable.

LowFunctions With `_` as Name Prefix Are Not `privatFixed

Functions with names starting with `_` should be declared as `private`/`internal`.

LowLack of Zero Address Validation of `ecrecover()`Fixed

The `ecrecover()` function can return the zero address (0x0000000000000000000000000000000000000000) if the signature was invalid or the message was malformed. When the smart contract does not check the output of `ecrecover()` and assumes it is always a valid address, vulnerability may arise.

LowMissing Emit EventsFixed

It is important to emit events for sensitive actions, particularly those that can be executed by centralized roles or administrators. This ensures transparency and enables tracking of critical changes, which is essential for security and trust in the system. Missing event logs can indeed result in a lack of visibility and potential information loss.

LowMissing Error MessagesFixed

The **require** can be used to check for conditions and throw an exception if the condition is not met. It is better to provide a string message containing details about the error that will be passed back to the caller.

LowNo Upper Limit for `feeNumerator`Fixed

The function `setDefaultRoyalty` receives the `feeNumerator` is used for royalty payment. In the marketplaces which support EIP-2981, `salePrice * ( feeNumerator/10000 `) will be sent to the royalty recipient. But there's no upper limit for `feeNumerator` when setting the default royalty and it can be as high as 100%.

Show 6 more
LowPotential Memory Overwriting on `guardedArrayReplFixed

In the library `ArrayUtils`, there is a function `guardedArrayReplace()` that is used to replace bytes in an array with bytes in another array, guarded by a bitmask. This function should process the array in two steps: quotient and remainder. The local variable `words` is used to separate the two steps, and it is calculated by `array.length / 0x20`. The first quotient step works well following this calculation. However, the later remainder case also uses this value to determine if there is a set of leftover bytes by checking `words != 0`. Since `words` holds the quotient, the remainder is not properly handled, causing issues when the function processes the `words != 0` condition. This bug can lead to arbitrary storage writes. If `array.length` is exactly divisible by `0x20`, the copying happens correctly within the loop. In other cases, the function enters the flawed logic and attempts to copy an extra word beyond the array bounds, leading to out-of-bounds access. This incorrect logic can be exploited to overwrite memory at the end of the target array, potentially affecting any unknown memory area.

LowPublic Drop Start Time and End Time Cannot Be theFixed

The `_checkActive()` function would revert if `startTime == endTime`, which indicates that the `startTime` and `endTime` of any mint (Public, AllowList, or Signed) cannot be the same. However, in the `updatePublicDrop()` function, the check in line 568 - 573 allows `publicDrop.startTime == publicDrop.endTime`, which is inconsistent with the minting logic. This means that it is possible to set public drop parameters that is guaranteed to fail when users attempt to mint.

LowRemaining Native Tokens may not be Refunded to UsFixed

When a contract function accepts native tokens and executes a `.call()` or similar function that also forwards native tokens, it's important to check for and refund any remaining balance. This is because some of the supplied value may not be used during the call execution due to gas constraints, a revert in the called contract, or simply because not all the value was needed. If this remaining balance is not refunded, it will be locked in the contract.

LowTypos in CommentsFixed

There are several typos in the contracts, please see the locations above, where the words with typos are listed: - targer - implemenation

LowUse `.call()` Instead Of `.send()` Or `.transfer(Fixed

The `.send()` function intends to transfer an ETH amount with a fixed amount of 2300 gas. This function is not equipped to handle changes in the underlying `.send()` and `.transfer()` functions which may supply different amounts of gas in the future. Additionally, if the recipient implements a fallback function containing some sort of logic, this may inevitably revert, meaning the vault and owner of the contract will never be able to call certain sensitive functions. Reference: [Solidity: Sending Ether (transfer, send, call)](https://solidity-by-example.org/sending-ether/)

LowVariables That Could Be Declared as `constant`Fixed

The linked variables could be declared as constant since these state variables are never modified. Compared to regular state variables, the gas costs of constant variables are much lower.

Contracts in scope

No deployed address is recorded for this audit.

The report names a repository or files. Open the report to see its scope.

Repository and files the report names (51)

  • contracts/thegenerates/interfaces/ITheGenerates.sol
  • contracts/thegenerates/lib/ErrorsAndEvents.sol
  • contracts/vesting/UnseenVesting.sol
  • contracts/thegenerates/types/DataTypes.sol
  • contracts/thegenerates/interfaces/ICreatorToken.sol
  • contracts/fee-collector/FeeCollector.sol
  • contracts/marketplace/registry/ProxyRegistry.sol
  • contracts/thegenerates/interfaces/IContractMetadata.sol
  • contracts/uncn/uncn.sol
  • contracts/thegenerates/extensions/ContractMetadata.sol
  • contracts/marketplace/staticMarket/StaticMarket.sol
  • contracts/thegenerates/interfaces/ITransferValidator.sol
  • contracts/interfaces/IERC4907A.sol
  • contracts/fee-collector/BaseFeeCollector.sol
  • contracts/marketplace/UnseenExchange.sol
  • contracts/thegenerates/interfaces/IERC173.sol
  • contracts/marketplace/registry/ProxyRegistryInterface.sol
  • contracts/thegenerates/extensions/TheGeneratesImplementation.sol
  • contracts/marketplace/lib/MarketErrorsAndEvents.sol
  • contracts/interfaces/IBaseFeeCollector.sol
  • contracts/marketplace/exchange/Exchange.sol
  • contracts/marketplace/exchange/ExchangeCore.sol
  • contracts/thegenerates/extensions/TGenContract.sol
  • contracts/vesting/libraries/SVGElements.sol
  • contracts/thegenerates/abstract/MarketsPreapproved.sol
  • contracts/lib/ArrayUtils.sol
  • contracts/vesting/interfaces/IUnseenVestingNFTDescriptor.sol
  • contracts/interfaces/IFeeCollector.sol
  • contracts/marketplace/GlobalMaker.sol
  • contracts/lib/ERC1271.sol
  • contracts/vesting/interfaces/IUnseenVesting.sol
  • contracts/extensions/ERC4907A.sol
  • contracts/vesting/libraries/NFTSVG.sol
  • contracts/lib/ERC1271Mod.sol
  • contracts/thegenerates/interfaces/ITheGeneratesConfigurer.sol
  • contracts/thegenerates/TheGenerates.sol
  • contracts/marketplace/UnseenStatic.sol
  • contracts/thegenerates/lib/TheGeneratesStorage.sol
  • contracts/vesting/abstracts/VestingLockup.sol
  • contracts/lib/StaticCaller.sol
  • contracts/vesting/UnseenVestingNFTDescriptor.sol
  • contracts/thegenerates/TheGeneratesConfigurer.sol
  • contracts/vesting/libraries/Helpers.sol
  • contracts/marketplace/UnseenRegistry.sol
  • contracts/marketplace/registry/AuthenticatedProxy.sol
  • contracts/marketplace/UnseenAtomicizer.sol
  • contracts/vesting/libraries/Errors.sol
  • contracts/interfaces/IBaseFeeCollectorEventsAndErrors.sol
  • contracts/vesting/types/DataTypes.sol
  • contracts/thegenerates/abstract/TokenTransferValidator.sol
  • contracts/vesting/interfaces/IVestingLockup.sol

Part of The Unseen’s security record

The Unseen
Audits1 audit by 1 firmLast audit Aug 2024, by CertiK.
Critical findings1 open critical findingReported in “Unseen - Audit” by CertiK, Aug 2024. Not marked fixed in that report.
Open The Unseen’s security record

Security firm

CertiK

1,044 projects reviewed · 1,375 reports · latest May 2025

See CertiK’s audits

Something wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.

Tell us