Double

CertiKFiles named in the report

CertiK reviewed Double on 20 Jan 2023: 15 findings, 1 critical; 12 marked fixed, 3 acknowledged.

1 critical finding, fixed
Open the reportOpen Double’s security record

Report hosted on skynet.certik.com.

Trustblock does not review code.

Findings15

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

SeverityOpenAcknowledgedFixed
Critical001
High022
Medium001
Low018
CriticalIrredeemable NFTFixed

When the token type is ERC5006, the NFT will be locked in the bank. When creating user records and the token type is ERC5006, the record owner is set as `address(this)` which is the Bank1155 contract. ```solidity=75 if (param.tokenType == TokenType.ERC5006) { recordId = IERC5006(addr5006).createUserRecord( address(this), // this is record owner param.user, param.oNFTId, uint64(param.oNFTAmount), uint64(param.expiry) ); ... ``` When deleting user records and the token type is ERC5006, the NFT should be sent back to the real owner who stakes it to the bank. But it is transferred from `address(this)` to `record.owner`. The NFT is still in the bank as the `record.owner` is also bank address. ```solidity=123 } else { IERC5006(addr5006).deleteUserRecord(param.recordId); IERC1155(param.oNFT).safeTransferFrom( address(this), record.owner, // record.owner is bank record.tokenId, record.amount, "" ); } ... ```

HighLack of Access ControlFixed

The functions `createUserRecord()` and `deleteUserRecords()` can be called by anyone as it has no access restriction. This enables anyone to call the function `deleteUserRecords()` and delete unexpired rental records. Similarly, anyone can call `createUserRecord()` and create rental records without payment if the NFT owner happens to allow the bank to transfer the NFT. Both risks could seriously harm the interests of renter and lender.

HighMissing Expire ValidationFixed

The bank contract does not check whether the rental records are indeed expired before deleting them. If unexpired records are deleted, the interests of renter are compromised.

MediumPotential Reentrancy AttackFixed

A reentrancy attack can occur when the contract creates a function that makes an external call to another untrusted contract before resolving any effects. If the attacker can control the untrusted contract, they can make a recursive call back to the original function, repeating interactions that would have otherwise not run after the external call resolved the effects.

LowInsufficient if-else ClauseFixed

The cited if-else clause is not insufficient. If the token type is not ERC721, it may also not be ERC4907.

LowLack of Storage Gap in Upgradeable ContractFixed

There is no storage gap preserved in the logic contract. Any logic contract that acts as a base contract that needs to be inherited by other upgradeable child should have a reasonable size of storage gap preserved for the new state variable introduced by the future upgrades.

LowMissing Input ValidationFixed

It should be checked that the parameter `oNFT` is ERC1155 and is not ERC5006.

LowMissing Zero Address ValidationFixed

Addresses should be checked before assignment or an external call to make sure they are not zero addresses. **Bank.sol** --- ```solidity=17 market = market_; ``` - `market_` is not zero-checked before being used. **W4907Factory.sol** --- ```solidity=28 w4907Impl = w4907Impl_; ``` - `w4907Impl_` is not zero-checked before being used. **W5506Factory.sol** --- ```solidity=22 w5006Impl = w5006Impl_; ``` - `w5006Impl_` is not zero-checked before being used. **Bank721.sol** --- ```solidity=15 _initOwnable(owner_, admin_); ``` - `owner_` is not zero-checked before being used. ```solidity=45 address w4907 = oNFT_w4907[oNFT]; ``` - `w4907` is not zero-checked before being used. **Bank1155.sol** --- ```solidity=21 _initOwnable(owner_, admin_); ``` - `owner_` is not zero-checked before being used. **BaseRentalMarket.sol** --- ```solidity=37 _initOwnable(owner_, admin_); ``` - `owner_` is not zero-checked before being used.

LowPotential Risks of `deployW4907()`Fixed

According to the design, the function `deployW4907()` has no access restrictions, and anyone can establish a mapping relationship between a compliant ERC721 contract and the `w4907Impl` contract. However, since the mapping relationship cannot be modified, if an ERC721 contract is mapped to `w4907Impl` contract by someone, the admin cannot establish a mapping relationship between this ERC721 contract and other ERC4907 contracts through the function `registerW4907()`. This may present potential uncontrolled risks because the user may set incorrect name and symbol data in the deployment of the w4907 contract.

LowUnprotected InitializerFixed

One or more logic contracts do not protect their initializers. An attacker can call the initializer and assume ownership of the logic contract, whereby she can perform privileged operations that trick unsuspecting users into believing that she is the owner of the upgradeable contract. ```solidity=10 contract Bank1155 is Bank, W5006Factory, ERC1155Receiver, IBank1155 { ``` - `Bank1155` is an upgradeable contract that does not protect its initializer. ```solidity=16 function initialize( ``` - `initialize` is an unprotected initializer function. --- ```solidity=9 contract Bank721 is BaseBank721, W4907Factory { ``` - `Bank721` is an upgradeable contract that does not protect its initializer. ```solidity=10 function initialize( ``` - `initialize` is an unprotected initializer function. --- ```solidity=9 contract RentalMarket1155 is BaseRentalMarket, IRentalMarket1155 { ``` - `RentalMarket1155` is an upgradeable contract that does not protect its initializer. ```solidity=10 function initialize( ``` - `initialize` is an unprotected initializer function. --- ```solidity=10 contract RentalMarket721 is BaseRentalMarket, IRentalMarket721 { ``` - `RentalMarket721` is an upgradeable contract that does not protect its initializer. ```solidity=11 function initialize( ``` - `initialize` is an unprotected initializer function.

Show 2 more
LowUnsafe Integer CastFixed

```solidity=80 uint64(param.oNFTAmount), ``` - The type conversion `uint64(param.oNFTAmount)` from larger type uint256 to smaller type uint64 may truncate data. --- ```solidity=81 uint64(param.expiry) ``` - The type conversion `uint64(param.expiry)` from larger type uint256 to smaller type uint64 may truncate data. --- ```solidity=86 uint64(param.oNFTAmount), ``` - The type conversion `uint64(param.oNFTAmount)` from larger type uint256 to smaller type uint64 may truncate data. --- ```solidity=88 uint64(param.expiry) ``` - The type conversion `uint64(param.expiry)` from larger type uint256 to smaller type uint64 may truncate data. --- ```solidity=67 IERC4907(w4907).setUser(nft.tokenId, renter, uint64(expiry)); ``` - The type conversion `uint64(expiry)` from larger type uint256 to smaller type uint64 may truncate data. --- ```solidity=69 IERC4907(nft.token).setUser(nft.tokenId, renter, uint64(expiry)); ``` - The type conversion `uint64(expiry)` from larger type uint256 to smaller type uint64 may truncate data. --- ```solidity=99 durations[key].start = uint40(start); ``` - The type conversion `uint40(start)` from larger type uint256 to smaller type uint40 may truncate data. --- ```solidity=189 v = uint8(uint256(vs >> 255)) + 27; ``` - The type conversion `uint8(uint256(vs >> 255))` from larger type uint256 to smaller type uint8 may truncate data.

LowUnused Return ValueFixed

According to the design, when the renter or lender fulfills the order, the contract will validate the metadata of the NFTs included in the order via the function `_validateMetadata()`. **BaseRentalMarket.sol** ```solidity=142 function _validateMetadata(NFT calldata nft, Metadata calldata metadata) internal view { if (metadata.checker != address(0)) { IMetadataChecker(metadata.checker).check( nft.token, nft.tokenId, metadata.metadataHash ); } } ``` However, the outcome of the external call is not handled by the function `_validateMetadata()`. The external call is useless if the result is not validated.

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 (19)

  • contracts/market/BaseRentalMarket.sol
  • contracts/bank/Bank.sol
  • contracts/validater/EIP712.sol
  • contracts/bank/Bank721.sol
  • contracts/bank/BaseBank721.sol
  • contracts/constant/RentalStructs.sol
  • contracts/market/RentalMarket721.sol
  • contracts/bank/W5006Factory.sol
  • contracts/bank/IBank.sol
  • contracts/constant/TokenEnums.sol
  • contracts/bank/W4907Factory.sol
  • contracts/bank/IBank1155.sol
  • contracts/market/IRentalMarket1155.sol
  • contracts/market/RentalMarket1155.sol
  • contracts/bank/IBank721.sol
  • contracts/constant/BaseStructs.sol
  • contracts/market/IRentalMarket.sol
  • contracts/market/IRentalMarket721.sol
  • contracts/bank/Bank1155.sol

Part of Double’s security record

Double
Audits2 audits by 2 firmsLast audit Jan 2023, by CertiK.
Critical findingsNo open critical findingsAcross the 2 reports, 1 critical finding was reported. It is marked fixed by the firm that reported it.
Open Double’s security record

Security firm

CertiK

1,044 projects reviewed · 1,393 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