Aconomy

QuillAuditsFiles named in the report

Audit · firm’s term: audit report

QuillAudits reviewed Aconomy Marketplace (Aconomy) on 24 Jun 2024: 37 findings, 0 critical; 20 marked fixed, 17 open.

No open critical findings
Open the reportOpen Aconomy Marketplace’s security record

Report hosted on quillaudits.com.

Trustblock does not review code.

Findings37

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

SeverityOpenAcknowledgedFixed
Critical000
High000
Medium403
Low13017
MediumAdd support for fee-on-transfer tokens.Open

stake() and addStake() allows anyone to stake any ERC20 token. In case the ERC20 token deducts fees on transfer, the calculation while storing/incrementing the stakedAmount would be incorrect.E.g. The Validator stakes 100 tokens, the stakedAmount for that validator increased by 100 on L63. But while transferring tokens on L65 only 90 tokens were transferred (as 10 tokens deducted as some tax/fee by token contract). In this case, the increase of 100 on L63 is incorrect. So this can trick the owner into refunding 100 tokens to the validator address even though less than 100 tokens were transferred. (It should be noted that while transferring the tokens back to the validator with refundStake(), less will be transferred in case of fee deducting tokens.) In piNFTMethods.addERC20() allows validators to add any ERC20 token. In case the ERC20 token deducts fees on transfer, the calculation in updateERC20() for adding _value in erc20Balances mapping would be incorrect. There’s a similar possibility in both piMarket and NFTlendingBorrowing regarding the usage of fees deducting tokens. While this issue can be rare, we have decided to highlight the issue as there would be unpredictable possibilities for an open system like this when it comes to the types and incorrect implementations of ERC20 tokens.

MediumBorrower defaulting is unhandled by the smart contractsOpen

The isLoanDefaulted() function is currently unused in the codebase, while in an isolated event this wouldn't be an issue but the poolAddress contract has no checks for borrowers who default on loans. As it stands, nothing stands between a defaulting borrower and a new loan being approved - not even with the scrutiny of the proposed lender. Within the loanRequest() function, there isn't any check/flag that indicates the borrower submitting a loan request has any bad debt. This isn't taken into consideration when the lender calls AcceptLoan(). The borrower's active loans are added to when in the actual sense, previous loans could be past their expiration time, thus defeating the purpose of a liquidation which occurs when liquidators (who are usually incentivized) clear bad debts from the system. In both FundingPool and poolAddress the precaution needs to be taken if the borrower never returns the taken loan back.

MediumDenial of service while paying monthly installmentOpen

When the lender supplies the amount to the pool with supplyToPool() it's getting checked that the _amount is greater or equal to 1000000.But when the poolOwner tries to repay the monthly amount after accepting the lender's bid, in the repayMonthlyInstallment() on L528 it's getting checked that fundDetail.amount / fundDetail.installment.installments greater or equal to 1000000. Let's say the lender entered _amount as 1000000 for the supplyToPool(), the check on L184 passed as the _amount is equal to 1000000. Then while paying the monthly installment on L528 it will revert as in this case fundDetail.amount / fundDetail.installment.installments would be 1e6/6 = 166666, which is less than 1000000. Resulting in denial of service while paying the monthly amount. While there can be an alternate solution to repay the full amount, the monthly repayment functionality would be useless. A similar situation occurs in poolAddress.sol with the loanRequest amount being checked to be greater or equal to 1000000, and repayMonthlyInstallment checks that the principal divided by the number of installments is greater or equal to 1000000 (1e6). If the borrower takes a loan worth 1000000 (1e6) tokens, he would be unable to repay the monthly

MediumUsers can take non collateralized loansOpen

The users can take non collateralized loans in the Fundingpool and poolAddress. The ability to take non collateralized loans creates the possibility where the borrowers may never return the loans.

LowAnyone can mint NFTs.Open

piNFT and validatedNFT allow functionality to mint NFTs. both piNFT.mintNFT() and validatedNFT.mintValidatedNFT() allows anyone to mint the NFTs.Assuming that these NFTs represent the on-chain presence of real-world tokens, allowing anyone to create/mint the tokens on-chain through these contracts may affect the business logic.

LowBorrower should be aware about the timeframeOpen

There is a possible scenario where the lender adds the bid with short bidDetail.expiration and can claim NFT directly after the block.timestamp goes ahead of bidDetail.expiration. So here the borrower gets less (or say inconsistent) time frame to pay the loan and interest amount.In this case showing the timeframe on frontend in which the borrower needs to pay the loan would be helpful, so that the borrower would be aware while/before accepting the loan that after the block.timestamp goes ahead of bidDetail.expiration the lender can claim his(borrower's) NFT.

LowCheck the intended installment amount that should be returnedOpen

In FundingPool on L504 if (monthsSinceStart > lastPaymentCycle) is true then it returns the fundDetail.paymentCycleAmount that would be the amount for the 1 cycle/month but it can happen that more than one months are elapsed after the last repayment in this case the amount needs to be adjusted accordingly.The same thing is happening in the poolAddress’s viewInstallmentAmount().

LowGeneral infoOpen

Nft is not getting transferred from the NFT owner to the contract when the validator is adding ERC20 with addERC20(). In this case the validator needs to trust the NFT owner, NFT owner may decide to not call redeemOrBurnPiNFT() assuming that the user is already the owner of NFT.

LowInadequate error logging in the contractsOpen

As a way of identifying irregularities or where certain requirements are not met, errors are logged in smart contracts. Many of the require statements currently do not have error messages attached to them. Functions could revert without the user knowing/receiving adequate feedback. Adding error messages can also help while handling exceptions on the front end.

Lowlender and borrower verification functionality can be simplified.Open

Instead of attestation functionality simple mapping for address to boolean can be used to verify lender and borrower addresses in the poolRegistry. In this way the functionality would be more simple to understand and gas efficient. Eventually the one who can add the lender and borrower would be the same address i.e. pool owner. In turn it will reduce the attack surface.

Show 7 more
LowNote about next due date calculationOpen

In both FundingPool.sol, poolAddress.sol the calculateNextDueDate() calculates the very next due date without taking the current timestamp in account. So it can happen that according to the current timestamp more than 1 due date has elapsed but it will still calculate the very next due date from the last repaid timestamp.

LowNote regarding the lending and the usage of any NFT in NFTlendingBorrowingOpen

The lenders can check the added amount for specific piNFT for which they are bidding. Doing so helps the lenders to get some assurance that even if the borrower is unable to repay the taken loan+interest, the borrower’s NFT can be claimed and this situation can be settled with the validator in real world and or with withdrawing the ERC20 from piNFTMethods.In the case the borrower is the owner of NFT with no ERC20 added for his NFT in the piNFTMethods, the lenders wouldn’t be able to get the assurance that they will get in the case when the borrower would be the user with ERC20 value added to his piNFT. And in the case of the borrower being unable to pay the loan, the lender has no options to settle this situation. Additionally it should be noted that the NFTlendingBorrowing allows users to list the other NFTs apart from the piNFT.

LowRedundant require statements:Open

In PoolRegistry.sol#L384, _attestAddress() has a require check that is repeated in the public functions (addLender, addBorrower) that call this internal function. The public functions have the ownsPool modifier which performs the exact same check. This will make the function calls to add lenders and borrowers to the protocol consume more gas than required.Same happens in poolAddress.sol#L405, where the private _repayFullLoan() function has the same require(loans[_loanId].state == LoanState.ACCEPTED); check as repayMonthlyInstalment().

LowRemove the redundant functionalityOpen

In addERC20(), the code checks that the validator is adding ERC20 that is equal to what was added for that collection and token previously,if different then it reverts on L207.This means the address type variable can be used in the erc20Contracts mapping instead of address array at the last, as it needs to only hold the one address. This in turn reduces other logic like the logic required for swapping and popping the elements in removeERC20(). Additionally, the address of the ERC20 can be hardcoded to simplify the process, instead of the validator passing it.

LowRestrict the contract functionalities to specific collectionsOpen

In piNFTMethods.sol, since anyone can add the validator for any NFT collection , there's a possibility that collection addresses that are not supported can be also used.In piMarket.sol, There’s similar possibility of using the NFTs from non supported collections for direct sale and auction. We recommend the project team to decide the appropriate case according to business logic and decide whether or not to allow any NFT collection address. In case only specific addresses/implementations are allowed. The functionality can be restricted to certain addresses.

LowUsage of any ERC20 token is possibleOpen

In piNFTMethods, the added validator can use any ERC20 token while adding ERC20 using addERC20(). Depending on the ERC20 token added this can be the ERC20 token with no value in the real market or the different ERC20 token than what the NFT owner is expecting.In NFTLendingBorrowing , The lender/bidder can specify the ERC20 token address that they are willing to transfer while bidding. And this is the ERC20 token that gets transferred while accepting the bid without checking if this is the same token that the NFT lister/borrower is willing to borrow. In piMarket, The seller is allowed to include any ERC20 token as _currency for their NFT sale. This functionality still varies from what is discussed with the team (i.e to allow only specific token e.g USDC to minimize the error).In FundingPool.sol, Lenders can use any ERC20 token address while lending the amount with supplyToPool().

LowUsers may decide to never return the validator’s withdrawn amount.Open

Users can withdraw the validator’s added ERC20 tokens using withdraw() , on the withdrawal of ERC20 tokens the msg.sender needs to transfer his NFT to the contract address. Which will be transferred back to him when he will repay the withdrawn ERC20 amount.It may happen that the user decides to not repay the validator’s withdrawn ERC20 amount (and the user never receives the NFT back). This scenario is unpredictable and factors like token values may affect it, But the audit team decided to highlight these scenarios for better assessment of business logic which in turn shares more information to the project team and its users.

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

  • contracts/Libraries/WadRayMath.sol
  • contracts/Libraries/DateTimeLib.sol
  • contracts/Libraries/LibPool.sol
  • contracts/Libraries/LibCollection.sol
  • contracts/utils/LibShare.sol
  • contracts/Libraries/LibCalculations.sol
  • contracts/Libraries/LibNFTLendingBorrowing.sol
  • contracts/Libraries/LibMarket.sol
  • contracts/Libraries/LibPoolAddress.sol

Part of Aconomy Marketplace’s security record

Aconomy Marketplace
Audits1 audit by 1 firmLast audit Jun 2024, by QuillAudits.
Critical findingsNo open critical findingsIn 1 report.
Open Aconomy Marketplace’s security record

Security firm

QuillAudits

350 projects reviewed · 459 reports · latest Apr 2025

See QuillAudits’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