TribeOne

Smart Contract Audit Report

Audit Summary

TribeOne Audit Report TribeOne is creating a new borrowing platform and NFT marketplace.

For this audit, we reviewed TribeOne's MultiSigWallet, AssetManager, and TribeOne contracts at commit cbc1190ef6fcea7de4466f0b2b3bd7391912efee on the team's private GitHub repository.

Audit Findings

Please ensure trust in the team prior to investing as they have substantial control in the ecosystem. Please note we have not reviewed the off-chain logic required to ensure proper functionality of the project.
Date: January 6th, 2022.
Updated: January 7th, 2022 to reflect changes from commit 059b04746d0af2acd61fb6994c00a5e520f6129b to commit ffc4e4771507d564848cd1ea2f86169c4bc65ea7.
Updated: January 18th, 2022 to reflect changes from commit ffc4e4771507d564848cd1ea2f86169c4bc65ea7 to commit cbc1190ef6fcea7de4466f0b2b3bd7391912efee

Finding #1 - TribeOne - High (Resolved)

Description: When paying an installment using a token, the passed amount is transferred to the AssetManager contract twice. Once from the user, and once from the TribeOne contract itself.
TribeOneHelper.safeTransferFrom(_loanCurrency, _msgSender(), assetManager, _amount);
IAssetManager(assetManager).collectInstallment(_loanCurrency, _amount, _loan.loanRules.interest, false);
Risk/Impact: The TribeOne contract will lose its funds over time until this function fails, as it is transferring its own funds to the AssetManager contract in addition to the user's funds.
Recommendation: The TribeOneHelper.safeTransferFrom(_loanCurrency, _msgSender(), assetManager, _amount); line should be removed.
Resolution: The team has fixed the above code so that the user's funds are first transferred to the TribeOne contract, then transferred to the AssetManager contract.

Finding #2 - TribeOne - Medium (Resolved)

Description: There is no incentive for users to liquidate defaulted loans.

Risk/Impact: If a defaulted loan is not liquidated quickly, the value of the NFT could depreciate and result in the platform selling at a loss. If a defaulted loan is never liquidated, the sales manager will not receive the NFT.
Recommendation: Provide an incentive for users to liquidate defaulted loans.
Resolution: The team explained that they will run off-chain logic to monitor defaulted loans and liquidate them accordingly. As we have not reviewed this logic, we cannot verify its security.

Finding #3 - TribeOne - Medium (Resolved)

Description: The createLoan() function will fail when ETH is used as a collateral currency. As the 0x0 address is used to specify ETH as the collateral currency, the safeTransferFrom() call will fail.
if (_collateralCurrency == address(0)) {
	require(msg.value >= _collateralAmount, "TribeOne: Insufficient collateral amount");
	if (msg.value > _collateralAmount) {
		TribeOneHelper.safeTransferETH(msg.sender, msg.value - _collateralAmount);
	}
}
TribeOneHelper.safeTransferFrom(_collateralCurrency, _msgSender(), address(this), _collateralAmount);
Risk/Impact: Users will not be able to create a loan with ETH as a collateral currency.
Recommendation: The above safeTransferFrom() call should be surrounded with an else block.
Resolution: The team has implemented the recommendation above so that safeTransferFrom() is not called when ETH is used as collateral.

Finding #4 - TribeOne - Low (Resolved)

Description: Any address can accept or reject an approved loan by calling the relayNFT() function.
Risk/Impact: While funds will be returned if a loan is rejected, gas from the creation and approval from the loan will be wasted if a user rejects another user's loan.
Recommendation: Only the borrower or the owner should be permitted to call relayNFT().
Resolution: The relayNFT function can now only be called by the owner. Borrowers must ensure trust in the team to call this function so that their collateral does not get stuck in this contract.

Contracts Overview

  • As the contracts are implemented with Solidity 0.8.x, they are protected from overflows.
  • ReentrancyGuard is utilized in the contracts where applicable, protecting them from any potential reentrancy attacks.
  • As much of the functionality of the platform relies on out of scope logic, we cannot verify that the platform will work as intended.
MultiSigWallet Contract:
  • Upon deployment, a list of signers and minimum required number of signatures for verification are passed.
  • Any logic can be executed from this contract through a signer submitting a transaction.
  • For each submitted transaction, a list of signatures must be provided and verified to originate from the list of signers.
  • Additionally, each signature must be from a unique signer.
  • The passed signature list length must be at least the length of the minimum required number of signatures.
AssetManager Contract:
  • This contract is used to store, transfer, and manage the platform's supported assets.
  • When a loan occurs, this contract is used to validate that the token is supported and that the loan amount is less than a defined "automatic loan limit".
  • The owner can add or remove support for any loan asset or collateral asset at any time.
  • A specified 'consumer' address has the ability to withdraw any tokens or ETH from this contract at any time.
  • The owner can update the consumer address at any time.
  • The owner can update the TWAP Oracle used for the pricing of a specified asset at any time.
  • The owner can update the automatic loan limit to any amount at any time.
  • The owner can withdraw any ETH or tokens from this contract at any time.
TribeOne Contract:
  • Any user can attempt to create a loan with a proposed loan asset, fund amount, collateral currency, and NFTs to be purchased. This can include both ERC721 and ERC1155 tokens.
  • Borrowers will also propose a specified interest percentage, tenor period (a tenor period of one is equal to four weeks), and loan-to-value ratio (LTV). These loan properties can be set to any amount.
  • The proposed collateral amount will then be transferred from the borrower to this contract.
  • ETH can be specified as the loan asset or collateral currency by inputting the 0 address.
  • Once a loan has been created, it must be approved by the owner or an Admin.
  • The borrower can cancel their created loan and regain their deposited funds if the loan has not yet been approved.
  • When approving, an agent address and amount are specified.
  • If the loan is approved, the amount passed will be transferred to the agent address from the AssetManager contract.
  • The loan's asset amount is then set to the amount passed minus the loan's fund amount.
  • If the amount passed is less than or equal to the borrower's fund amount, the loan's status will be set to Rejected and the borrower's collateral will be returned to them.
  • The approval will fail if the calculated price of the NFTs, which is based on the fund amount and LTV, is greater than the AssetManager's automatic loan limit.
  • An NFT's price is calculated in USDT using an external price oracle outside the scope of this audit.

  • Once a loan is approved, the owner can either accept or reject the loan. When accepted, the loan's status will be set to "Activated". The specified NFTs will then be transferred from the agent to this contract. The loan's collateral amount will also be transferred from this contract to the AssetManager.
  • If the loan is denied, the loan's status will be set to "Failed" and collateral will be returned to the borrower. In addition, the amount originally transferred to the agent will be transferred back from them. Note that if the agent does not approve this contract to transfer their funds, the refund will fail.
  • The borrower should ensure trust in the team does not call the function to accept or reject their loan, their collateral deposit will be stuck in the contract.
  • Once a loan is Activated, any user can make payments towards it. The loan's tenor period is divided into "tenor units" of 4 weeks, where certain payment thresholds must be reached before each tenor unit.
  • The loan's calculated interest amount is added to the total required payment amount.
  • Payments are stored in the AssetManager contract.
  • Once a loan has been fully paid, a borrower can withdraw the NFTs from this contract.
  • The borrower will incur a specified late fee each time a tenor unit passes and its payment threshold has not been reached.
  • Any late fees must be paid at the time of withdrawing in the contract's fee token.
  • Late fees must be paid in a specified fee token and are sent to a fee address.
  • Any address can set any activated loan to the "Defaulted" status if payments are overdue. A borrower can revert their loan's status to Activated if any payment is made towards it.
  • Any user can "liquidate" any defaulted loan if a specified grace period of 14 days has passed since their last overdue payment. Liquidating results in the transfer of the NFTs to a "sales manager" address.
  • After liquidation, the sales manager address can trigger a "post-liquidation" on the loan. The sales manager will transfer their own funds to the fee address. If the amount sent exceeds the loan's remaining debt plus a specified penalty amount, the excess can be later recovered by the borrower.
  • The borrower has a grace period of 14 days to recover their funds and must pay a late fee in the form of the contract's fee token.
  • If a user elects not to recover their funds, remaining funds will be sent to the fee address once the grace period has passed.
  • As the user's loan's fund amount is never transferred in this contract, the team should ensure that these funds are tracked appropriately if a borrower is refunded or if their loan is activated.

  • While ownership is transferred to the MultiSigWallet contract upon deployment, a "super owner" role is granted to the contract deployer. The super owner can transfer ownership or super ownership to any address at any time.
  • The super owner can also grant and revoke Admin permissions to any address at any time.
  • Admins are exempt from the automatic loan limit.
  • The super owner can update the late fee, penalty fee, sales manager address, AssetManager address, and fee address at any time.

Audit Results

Vulnerability CategoryNotesResult
Arbitrary Jump/Storage WriteN/APASS
Centralization of Control
  • The owner and Admins have the permissions mentioned above.
  • The super owner has the ability to transfer ownership and grant/revoke admin privileges.
  • The owner or an Admin can withdraw any amount of funds from the AssetManager contract.
  • Off-chain logic is used to keep track of a borrower's fund amount and to activate an approved loan so that a borrower can repay their loan and not lose their collateral deposit.
WARNING
Compiler IssuesN/APASS
Delegate Call to Untrusted ContractN/APASS
Dependence on Predictable VariablesN/APASS
Ether/Token TheftN/APASS
Flash LoansN/APASS
Front RunningN/APASS
Improper EventsN/APASS
Improper Authorization SchemeN/APASS
Integer Over/UnderflowN/APASS
Logical IssuesN/APASS
Oracle IssuesN/APASS
Outdated Compiler VersionN/APASS
Race ConditionsN/APASS
ReentrancyN/APASS
Signature IssuesN/APASS
Unbounded LoopsN/APASS
Unused CodeN/APASS
Overall Contract Safety PASS

MultiSigWallet Contract

Smart Contract Audit - Inheritance

Smart Contract Audit - Graph


 ($) = payable function
 # = non-constant function
 
 Int = Internal
 Ext = External
 Pub = Public
 
 +  ReentrancyGuard 
    - [Pub]  #

 + [Lib] Counters 
    - [Int] current
    - [Int] increment #
    - [Int] decrement #

 +  MultiSigWallet (ReentrancyGuard)
    - [Pub]  #
    - [Ext]  ($)
    - [Ext] submitTransaction ($)
       - modifiers: onlySigner,nonReentrant
    - [Prv] _getSigner
    - [Ext] getSigners
    - [Ext] getTransactionCount

AssetManager Contract

Smart Contract Audit - Inheritance

Smart Contract Audit - Graph


 ($) = payable function
 # = non-constant function
 
 Int = Internal
 Ext = External
 Pub = Public
 
 + [Int] IERC20 
    - [Ext] totalSupply
    - [Ext] balanceOf
    - [Ext] transfer #
    - [Ext] allowance
    - [Ext] approve #
    - [Ext] transferFrom #

 + [Int] IERC20Metadata (IERC20)
    - [Ext] name
    - [Ext] symbol
    - [Ext] decimals

 +  Context 
    - [Int] _msgSender
    - [Int] _msgData

 +  Ownable (Context)
    - [Pub]  #
    - [Pub] owner
    - [Pub] renounceOwnership #
       - modifiers: onlyOwner
    - [Pub] transferOwnership #
       - modifiers: onlyOwner

 +  ReentrancyGuard 
    - [Pub]  #

 + [Int] IAssetManager 
    - [Ext] isAvailableLoanAsset #
    - [Ext] isAvailableCollateralAsset #
    - [Ext] isValidAutomaticLoan #
    - [Ext] requestETH #
    - [Ext] requestToken #

 + [Int] ITwapOraclePriceFeed 
    - [Ext] token0 #
    - [Ext] token1 #
    - [Ext] consult

 + [Int] IERC165 
    - [Ext] supportsInterface

 + [Int] IERC721 (IERC165)
    - [Ext] balanceOf
    - [Ext] ownerOf
    - [Ext] safeTransferFrom #
    - [Ext] transferFrom #
    - [Ext] approve #
    - [Ext] getApproved
    - [Ext] setApprovalForAll #
    - [Ext] isApprovedForAll
    - [Ext] safeTransferFrom #

 + [Int] IERC1155 (IERC165)
    - [Ext] balanceOf
    - [Ext] balanceOfBatch
    - [Ext] setApprovalForAll #
    - [Ext] isApprovedForAll
    - [Ext] safeTransferFrom #
    - [Ext] safeBatchTransferFrom #

 + [Lib] TribeOneHelper 
    - [Int] safeApprove #
    - [Int] safeTransfer #
    - [Int] safeTransferFrom #
    - [Int] safeTransferETH #
    - [Int] safeTransferAsset #
    - [Int] safeTransferNFT #
    - [Int] getExpectedPrice

 +  AssetManager (Ownable, ReentrancyGuard, IAssetManager)
    - [Pub]  #
    - [Ext]  ($)
    - [Ext] consumer
    - [Ext] addAvailableLoanAsset #
       - modifiers: onlyOwner,nonReentrant
    - [Ext] removeAvailableLoanAsset #
       - modifiers: onlyOwner,nonReentrant
    - [Ext] addAvailableCollateralAsset #
       - modifiers: onlyOwner,nonReentrant
    - [Ext] removeAvailableCollateralAsset #
       - modifiers: onlyOwner,nonReentrant
    - [Ext] isAvailableLoanAsset
    - [Ext] isAvailableCollateralAsset
    - [Ext] setConsumer #
       - modifiers: onlyOwner
    - [Ext] setLoanAssetTwapOracle #
       - modifiers: onlyOwner,nonReentrant
    - [Ext] setAutomaticLoanLimit #
       - modifiers: onlyOwner
    - [Ext] isValidAutomaticLoan
    - [Ext] requestETH #
       - modifiers: onlyConsumer
    - [Ext] requestToken #
       - modifiers: onlyConsumer
    - [Ext] withdrawAsset #
       - modifiers: onlyOwner

TribeOne Contract

Smart Contract Audit - Inheritance

Smart Contract Audit - Graph


 ($) = payable function
 # = non-constant function
 
 Int = Internal
 Ext = External
 Pub = Public
 
 + [Int] IERC20 
    - [Ext] totalSupply
    - [Ext] balanceOf
    - [Ext] transfer #
    - [Ext] allowance
    - [Ext] approve #
    - [Ext] transferFrom #

 + [Int] IERC20Metadata (IERC20)
    - [Ext] name
    - [Ext] symbol
    - [Ext] decimals

 +  ReentrancyGuard 
    - [Pub]  #

 + [Lib] Counters 
    - [Int] current
    - [Int] increment #
    - [Int] decrement #

 + [Int] IERC721Receiver 
    - [Ext] onERC721Received #

 +  ERC721Holder (IERC721Receiver)
    - [Pub] onERC721Received #

 + [Int] IERC165 
    - [Ext] supportsInterface

 + [Int] IERC1155Receiver (IERC165)
    - [Ext] onERC1155Received #
    - [Ext] onERC1155BatchReceived #

 +  ERC165 (IERC165)
    - [Pub] supportsInterface

 +  ERC1155Receiver (ERC165, IERC1155Receiver)
    - [Pub] supportsInterface

 +  ERC1155Holder (ERC1155Receiver)
    - [Pub] onERC1155Received #
    - [Pub] onERC1155BatchReceived #

 + [Int] ITribeOne 
    - [Ext] approveLoan #
    - [Ext] relayNFT ($)

 + [Int] IAssetManager 
    - [Ext] isAvailableLoanAsset #
    - [Ext] isAvailableCollateralAsset #
    - [Ext] isValidAutomaticLoan #
    - [Ext] requestETH #
    - [Ext] requestToken #

 +  Context 
    - [Int] _msgSender
    - [Int] _msgData

 +  Ownable (Context)
    - [Pub]  #
    - [Pub] owner
    - [Ext] superOwner
    - [Pub] isAdmin
    - [Pub] renounceOwnership #
       - modifiers: onlyOwner
    - [Pub] transferOwnership #
       - modifiers: onlySuperOwner
    - [Pub] transferSuperOwnerShip #
       - modifiers: onlySuperOwner
    - [Ext] addAdmin #
       - modifiers: onlySuperOwner
    - [Ext] removeAdmin #
       - modifiers: onlySuperOwner

 + [Int] IERC721 (IERC165)
    - [Ext] balanceOf
    - [Ext] ownerOf
    - [Ext] safeTransferFrom #
    - [Ext] transferFrom #
    - [Ext] approve #
    - [Ext] getApproved
    - [Ext] setApprovalForAll #
    - [Ext] isApprovedForAll
    - [Ext] safeTransferFrom #

 + [Int] IERC1155 (IERC165)
    - [Ext] balanceOf
    - [Ext] balanceOfBatch
    - [Ext] setApprovalForAll #
    - [Ext] isApprovedForAll
    - [Ext] safeTransferFrom #
    - [Ext] safeBatchTransferFrom #

 + [Lib] TribeOneHelper 
    - [Int] safeApprove #
    - [Int] safeTransfer #
    - [Int] safeTransferFrom #
    - [Int] safeTransferETH #
    - [Int] safeTransferAsset #
    - [Int] safeTransferNFT #
    - [Int] getExpectedPrice

 +  TribeOne (ERC721Holder, ERC1155Holder, ITribeOne, Ownable, ReentrancyGuard)
    - [Pub]  #
    - [Ext]  ($)
    - [Ext] getLoanAsset
    - [Ext] getCollateralAsset
    - [Ext] getLoanRules
    - [Ext] getLoanNFTCount
    - [Ext] getLoanNFTItem
    - [Ext] setSettings #
       - modifiers: onlyOwner
    - [Ext] createLoan ($)
    - [Ext] approveLoan #
       - modifiers: onlyOwner,nonReentrant
    - [Ext] relayNFT ($)
       - modifiers: onlyOwner,nonReentrant
    - [Ext] payInstallment ($)
       - modifiers: nonReentrant
    - [Ext] withdrawNFT #
       - modifiers: nonReentrant
    - [Prv] _withdrawNFT #
    - [Prv] _updatePenalty #
    - [Pub] totalDebt
    - [Prv] expectedNrOfPayments
    - [Pub] expectedLastPaymentTime
    - [Ext] setLoanDefaulted #
       - modifiers: nonReentrant
    - [Ext] setLoanLiquidation #
       - modifiers: nonReentrant
    - [Ext] postLiquidation ($)
       - modifiers: nonReentrant
    - [Pub] finalDebtAndPenalty
    - [Ext] getBackFund ($)
    - [Ext] lockRestAmount #
       - modifiers: nonReentrant
    - [Ext] cancelLoan #
       - modifiers: nonReentrant
    - [Prv] returnColleteral #

About SourceHat

SourceHat has quickly grown to have one of the most experienced and well-equipped smart contract auditing teams in the industry. Our team has conducted 1800+ solidity smart contract audits covering all major project types and protocols, securing a total of over $50 billion U.S. dollars in on-chain value!
Our firm is well-reputed in the community and is trusted as a top smart contract auditing company for the review of solidity code, no matter how complex. Our team of experienced solidity smart contract auditors performs audits for tokens, NFTs, crowdsales, marketplaces, gambling games, financial protocols, and more!

Contact us today to get a free quote for a smart contract audit of your project!

What is a SourceHat Audit?

Typically, a smart contract audit is a comprehensive review process designed to discover logical errors, security vulnerabilities, and optimization opportunities within code. A SourceHat Audit takes this a step further by verifying economic logic to ensure the stability of smart contracts and highlighting privileged functionality to create a report that is easy to understand for developers and community members alike.

How Do I Interpret the Findings?

Each of our Findings will be labeled with a Severity level. We always recommend the team resolve High, Medium, and Low severity findings prior to deploying the code to the mainnet. Here is a breakdown on what each Severity level means for the project:

  • High severity indicates that the issue puts a large number of users' funds at risk and has a high probability of exploitation, or the smart contract contains serious logical issues which can prevent the code from operating as intended.
  • Medium severity issues are those which place at least some users' funds at risk and has a medium to high probability of exploitation.
  • Low severity issues have a relatively minor risk association; these issues have a low probability of occurring or may have a minimal impact.
  • Informational issues pose no immediate risk, but inform the project team of opportunities for gas optimizations and following smart contract security best practices.