Pichi Finance

Pichi Finance Marketplace audit report

Pichi Finance

SUMMARY

Critical 0
High 0
Medium 0
Low 6
Advisory 9
Total: 15

ABSTRACT

Dedaub was commissioned to perform a security audit of Pichi Finance’s Marketplace contracts that offers a marketplace for points via NFTs, ensuring secure transactions with no collateral required.

BACKGROUND

The Pichi Finance protocol allows users to create ERC-6551 (tokenbound) accounts to earn points. Each account is owned by an NFT and can be traded through the Pichi marketplace by trading the NFT.

The protocol consists of three main contracts:

  1. The PichiWalletNFT contract allows the minting of NFTs at a price set by the owner of the contract.
  2. The PichiHelper contract uses the PichiWalletNFT contract to simultaneously mint NFTs and create the tokenbound accounts controlled by these NFTs. It also provides functionality for depositing whitelisted tokens into a wallet controlled by the owner of an NFT, potentially against a deposit fee.
  3. The PichiMarketplace contract allows the trading of NFTs from whitelisted collections for various whitelisted currencies. A buyer can either execute a listing which was signed off-chain by a seller, or alternatively a seller can accept an offer which was signed off-chain by a buyer, using the EIP-712 signature scheme. A user can cancel their orders individually or in bulk by updating a nonce. The contract also has functionality to validate off-chain the above off-chain orders, as well as to handle the transfer of NFTs and settle payments.

SETTING & CAVEATS

This audit report mainly covers the contracts of the michi-contracts repository of the Pichi Finance protocol at commit d9cee365b59ed7798a8f958cd8b23574c1e08b9a.

Two auditors worked on the codebase for 2 days on the following contracts:

src/
├── PichiHelper.sol
├── PichiWalletNFT.sol
├── interfaces/
│   ├── IPichiMarketplace.sol
│   └── IPichiWalletNFT.sol
├── libraries/
│   ├── OrderTypes.sol
│   └── SignatureAuthentication.sol
└── marketplace/
    └── PichiMarketplace.sol

Prior to the audit the Pichi team communicated to the audit team that there is a known “issue” in the PichiHelper contract allowing users to bypass the deposit fee. The Pichi team has indicated that for the moment the deposit fee is set to 0. If in the future it is increased, Pichi will reward users with additional benefits in case they choose to pay it.

The audit’s main target is security threats, i.e., what the community understanding would likely call “hacking”, rather than the regular use of the protocol. Functional correctness (i.e. issues in “regular use”) is a secondary consideration. Typically it can only be covered if we are provided with unambiguous (i.e. full-detail) specifications of what is the expected, correct behavior. In terms of functional correctness, we often trusted the code’s calculations and interactions, in the absence of any other specification. Functional correctness relative to low-level calculations (including units, scaling and quantities returned from external protocols) is generally most effectively done through thorough testing rather than human auditing.

PROTOCOL-LEVEL CONSIDERATIONS

TBA trustless sales can be vulnerable to order misrepresentation

Protocol Level Consideration | Status: INFO

Token bound accounts are designed to hold different kinds of tokens and thus In order to enable trustless sales of TBAs, decentralized marketplaces such as PichiMarketplace will need to implement safeguards against fraudulent behavior by malicious account owners. Consider the following potential vulnerability taken from the Security Considerations section of ERC6551:

  • Alice owns an ERC-721 token X, which owns token bound account Y.
  • Alice deposits 10 ETH into account Y.
  • Bob offers to purchase token X for 11 ETH via a decentralized marketplace, assuming he will receive the 10 ETH stored in account Y along with the token.
  • Alice withdraws 10 ETH from the token bound account, and immediately accepts Bob’s offer.
  • Bob receives token X, but account Y is empty.

PichiMarketplace does not implement any mitigations for such fraudulent behavior on the contract level. Pichi implements the following alerts in the application’s UI (frontend) to inform and protect users of its marketplace:

  • A TBA owner who wants to list their TBA for sale on the PichiMarketplace, will be warned by Pichi’s UI prompts that they should withdraw all the tokens owned by the TBA before listing it for sale.
  • On the buyer side, there are prompts to inform the buyer that the owner of the TBA they are bidding on can withdraw tokens before accepting the offer and that they should make their offer solely on the Pichi points accrued in that TBA.

VULNERABILITIES & FUNCTIONAL ISSUES

This section details issues affecting the functionality of the contract. Dedaub generally categorizes issues according to the following severities, but may also take other considerations into account such as impact or difficulty in exploitation:

Issue resolution includes “dismissed” or “acknowledged” but no action taken, by the client, or “resolved”, per the auditors.

CRITICAL SEVERITY

[No Critical Severity Issues]

HIGH SEVERITY

[No High Severity Issues]

MEDIUM SEVERITY

[No Medium Severity Issues]

LOW SEVERITY

Missing check in initialize function of PichiMarketplace

Low | Status: RESOLVED

PichiMarketplace::setMarketplaceFee checks that the newFee <= 1000, but this is not enforced in PichiMarketplace::initialize for the marketplaceFee\_ parameter.

Missing check in constructor of PichiHelper

Low | Status: RESOLVED

PichiHelper’s setDepositFee function checks that the newDepositFee <= 500, but this is not enforced on the _depositFee parameter of the constructor.

Occurrence of owner mismatch when using PichiHelper::createWallet requires manual intervention to unblock

Low | Status: RESOLVED

PichiHelper::createWallet checks whether the owner of the newWallet != msg.sender and reverts if this is the case. Our understanding is that this can occur if another user coincidentally generates the same address when creating his own tokenbound account (potentially when using a different NFT contract).

However if this call reverts, the currentIndex of the NFT contract will not advance, because _mint will also revert, resulting in this error being reproduced during subsequent calls. Hence the wallet creation mechanism will be stuck until someone goes into the NFT contract and manually mints a dummy NFT to skip the problematic index.

Fee percentage’s precision should be hardcoded

Low | Status: RESOLVED

The variables PichiHelper::feePrecision and PichiMarketplace::precision, which represent the fee percentage’s precision (at 100%), can be initialized to an arbitrary value, possibly to something even smaller than the actual fee percentage. A definitive value should be chosen and hardcoded to increase the contracts’ clarity.

PichiMarketplace implementation contract’s initializer is not disabled

Low | Status: RESOLVED

The initializer function of the PichiMarketplace implementation contract should be disabled as suggested by the OpenZeppelin best practices. This can be done by calling the Initializable::_disableInitializers() function in the PichiMarketplace constructor.

constructor() {
    _disableInitializers();
}

The version of the OZ libraries used is not the latest

Low | Status: ACKNOWLEDGED

The protocol uses version 4.6 of the OpenZeppelin libraries instead of the latest standard that is 5.0.2.

CENTRALIZATION ISSUES

It is often desirable for DeFi protocols to assume no trust in a central authority, including the protocol’s owner. Even if the owner is reputable, users are more likely to engage with a protocol that guarantees no catastrophic failure even in the case the owner gets hacked/compromised. We list issues of this kind below. (These issues should be considered in the context of usage/deployment, as they are not uncommon. Several high-profile, high-value protocols have significant centralization threats.)

PichiMarketplace is upgradeable

Centralization | Status: ACKNOWLEDGED

The PichiMarketplace contract inherits from the OpenZeppelin OwnableUpgradeable contract meaning that the protocol owners are able to upgrade its implementation.

PichiHelper’s owner controls the ERC-6551 account creation

Centralization | Status: ACKNOWLEDGED

PichiHelper’s purpose is to ease the process of minting PichiWalletNFTs and of the creation of ERC-6551 accounts owned by the minted NFTs. This means that the PichiHelper owner defines the ERC-6551 registry, proxy and implementation used for the tokenbound account creation. The owner can also upgrade the proxy and implementation used for the creation at any time via the updateImplementation and updateProxy functions.

OTHER / ADVISORY ISSUES

This section details issues that are not thought to directly affect the functionality of the project, but we recommend considering them.

Improve naming of OrderAlreadyCancelled error in PichiMarketplace

Advisory | Status: RESOLVED

PichiMarketplace::cancelOrdersForCaller reverts with the error OrderAlreadyCancelled() both if the order has been canceled, and if it has been Executed. Consider improving the naming for better error readability.

Divergence between PichiMarketplace’s executeListing and acceptOffer functions in terms of the settlement currencies available

Advisory | Status: ACKNOWLEDGED

The PichiMarketplace contract has two functions which allow a match to be settled. The executeListing function allows a buyer to match a seller’s listing, while the acceptOffer function allows a seller to match a buyer’s offer. However, while executeListing allows settlement in the native token (ETH), the acceptOffer function does not. We recommend reviewing the asymmetry between these two methods of matching and to use a consistent settlement method.

PichiMarketplace’s use of WETH as a placeholder for ETH means WETH can never be a settlement currency.

Advisory | Status: RESOLVED

The PichiMarketplace contract expects the currency of a listing to be set to WETH as a placeholder, whenever the listing actually needs to be settled in ETH.

For instance PichiMarketplace::validateListing enforces the constraint that if the listing currency is WETH, then the msg.value (denominated in ETH) should be equal to the order amount.

This assumption is also present in PichiMarketplace::executeListing``, which will always call _transferWalletForPaymentwith theisETHparameter set to true whenever the listing’s order currency is set toWETH`.

This means that a listing can never be paid in actual WETH, but can be paid in any other allowed token. We recommend considering whether this is the intended behaviour of the system, as other placeholders such as address(0) could be used for this purpose.

Storage variables can be made immutable

Advisory | Status: RESOLVED

The storage variables pichiWalletNFT, erc6551Registry and feePrecision of the PichiHelper contract can be made immutable.

PichiMarketplace does not need to inherit from Initializable

Advisory | Status: RESOLVED

PichiMarketplace does not need to inherit from the OZ Initializable contract, as the OwnableUpgradeable contract already does so.

PichiMarketplace::cancelOrdersForCaller reverts if order has already been canceled.

Advisory | Status: RESOLVED

The function cancelOrdersForCaller of the PichiMarketplace contract reverts if orderNonces[i] <= userMinOrderNonce[msg.sender] holds or if the i-th order has already been canceled or executed via the isUserNonceExecutedOrCancelled mapping. In other words, if the set of orders to be canceled contains at least one canceled or executed order, the whole tx reverts. We consider this unnecessary and possibly confusing. If the tx reverts for the aforementioned reason and the caller/user does not realize it in order to resubmit a valid set of orders to be canceled, their orders will remain open when this is clearly not the desired behavior.

Extra space in domainSeparator computation in PichiMarketplace::initialize

Advisory | Status: RESOLVED

When the domainSeparator is computed by PichiMarketplace::initialize there is a space between chainId and address when there should not be one. We do not believe this is a real problem except if there is such a difference between the front and backend code.

Use of reinitializer(1) instead of initializer PichiMarketplace::initialize

Advisory | Status: RESOLVED

PichiMarketplace::initialize could use the reinitializer(1) modifier instead of the initializer modifier. This is to be consistent with future upgrades where you seem to intend to use reinitializer(2).

Compiler bugs

Advisory | Status: INFO

The code is compiled with Solidity 0.8.13. Version 0.8.13, in particular, has some known bugs, which we do not believe affect the correctness of the contracts.

DISCLAIMER

The audited contracts have been analyzed using automated techniques and extensive human inspection in accordance with state-of-the-art practices as of the date of this report. The audit makes no statements or warranties on the security of the code. On its own, it cannot be considered a sufficient assessment of the correctness of the contract. While we have conducted an analysis to the best of our ability, it is our recommendation for high-value contracts to commission several independent audits, a public bug bounty program, as well as continuous security auditing and monitoring through Dedaub Security Suite.

ABOUT DEDAUB

Dedaub offers significant security expertise combined with cutting-edge program analysis technology to secure some of the most prominent protocols in DeFi. The founders, as well as many of Dedaub’s auditors, have a strong academic research background together with a real-world hacker mentality to secure code. Protocol blockchain developers hire us for our foundational analysis tools and deep expertise in program analysis, reverse engineering, DeFi exploits, cryptography and financial mathematics.