As of 25 Oct 2026, Spheron Network has 3 audit reports by 1 security firm on Trustblock; the latest is by QuillAudits, dated 27 Mar 2025. Those reports list no open critical findings.
Record last updated . It holds audits and findings, contracts and incidents up to Oct 2026. Trustblock does not review code.
Newest first
| Audit | Security firm | Date | Scope | Critical findings | Report |
|---|---|---|---|---|---|
| Spheron change review Audit · firm’s term: review | Security firm: QuillAudits | Date: | Scope: Files named in the report | No open critical findings | Open · quillaudits.com |
| Spheron V2 | Security firm: QuillAudits | Date: | Scope: Files named in the report | No open critical findings | Open · quillaudits.com |
| Spheron V1 | Security firm: QuillAudits | Date: | Scope: Files named in the report | No open critical findings | Open · quillaudits.com |
Statuses are the security firm’s own, from each report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 0 | 0 | 3 |
| Medium | 6 | 0 | 5 |
| Low | 11 | 0 | 3 |
Taking Example of createLease()(same applies for closeAndRenewLease)<br>- Whenever <code>createLease()</code> gets called the function would throw error.<br>- <code>createLease()</code> gets called from <code>Bid.sol::matchOrder()</code>.<br>- In <code>createLease()</code> there is a <code>onlyBidContract()</code> modifier which passes as msg.sender is Bid<br>contract. Inside <code>createLease()</code>,<code>escrow.lockToken()</code> gets called :<br><code>escrow.lockTokens(order.creator, order.token, _toPrecision(leaseCostPlusFee, token.decimals));</code>- <code>lockTokens()</code> contains a <code>onlyRole(DEFAULT_ADMIN_ROLE)</code> which means only admin can call<br>this function but in our case <code>msg.sender</code> would be computeLease contract address so<br><code>lockTokens()</code> function will fail due to the<br><code>onlyRole(DEFAULT_ADMIN_ROLE)</code> modifier.<br>
Open the reportSome tokens have a fee on transfer, for example USDT. Usually such fee is not enabled but could be re-enabled at any time.<br>With this fee enabled the deposit() and withdraw() function would receive slightly less tokens than the amounts requested in the function parameter. This will create an accounting problem, as the whole <code>amount</code> variable gets added in unlock Tokens mapping but actually it will receive slightly less tokens than <code>amount</code>.<br>So in mapping it should add the actual received amount.<br>As main functionality is dependent on Lock and Unlock Tokens this should be handled correctly
Open the reportMalicious Admin can steal away provider earnings Whenever provider or user calls <code>closeLease()</code>, malicious admin can frontrun this transaction and increase the <code>protocolPerc</code> to a very high value such that the very major part of tokens as fees.<br>Now as protocolPerc has been set to almost 99% using<code>setProviderFeePercentage()</code> So provider would get very less amount of tokens as earnings and other major amount (99%) goes as fees to protocol.<br>Here providerAmount would be only 1% of total amount and protocolFee would 99% of total amount.<br>
Open the reportMalicious User would pay less tokens even if he has used the service provider by provider more than the specified time in the order.<br>Lets understand using an example:<br>1. There is a matched order between user and provider and the conditions are `order.numOfBlocks = 100` and `acceptedPrice = 1e18`<br>2. Let's say after passing of 200 blocks the provider calls `closeLease()` to close the lease.<br>3. As user has used the service for 200 blocks he should pay tokens for 200 blocks.<br>4. But user would only pay the tokens for 100 blocks of service.<br>5. So it would be loss to provider as he will not receive tokens for remaining 100 blocks.<br>6. According to our example loss of 100e18 to provider
Open the reportThere is no check while calling <code>withdrawProviderEarnings()</code> , <code>withdrawFizzNodeEarnings()</code> and <code>with drawProtocolFees</code> that the withdrawls are in pause state or not.If <code>pauseWithdrawal</code> has been set to true there should be no withdrawals from the protocol.But by calling <code>withdrawProviderEarnings()</code> , <code>withdrawFizzNodeEarnings()</code> and <code>withdrawProtocolFees</code> they can withdraw funds even though the <code>pauseWithdrawal</code> has been set to <a href="http://true.it/" target="_blank">true.It</a> breaks the core functionality of the protocol
Open the reportProvider would not be able to claim earnings if providerRewardWallet is set as contract address.Let's suppose a provider has added himself into registry and has set providerRewardWallet to contract address where he wants the fund.<br>Now when he wants to claim his earnings via Escrow.withdrawProviderEarnings() he would not be able to claim due to the below check <code>require(msg.sender == providerRewardWallet, "Not Provider Reward Wallet");</code> So due to above check the provider would not be able to claim as contract address doesn't have <code>withdrawProviderEarnings</code> function selector and whenever the provider calls <code>withdrawProviderEarn ings()</code> from his address it would revert due to above condition. <code>"Not Provider Reward Wallet"</code>
Open the reportThere should be a deadline set during signature verification otherwise the signature can be used for an indefinite period of time.
Open the reportThe above function should only be called by owner but it lacks modifier onlyOwner so anyone can initiate claiming of penaltyAssets, although the assets are being transferred to the owner so this is a low issue. And emit PenaltyAssetsDistributed(msg.sender, penaltyAssets); event is emitted with msg.sender which is wrong if anyone else apart from the owner calls this function.<br><code>solidity function claimPenaltyAssets() external { require(penaltyShares > 0, "No penalty shares"); uint256 penaltyAssets = pToken.convertToAssets(penaltyShares); penaltyShares = 0; pToken.transfer(owner(), penaltyAssets); emit PenaltyAssetsDistributed(msg.sender, penaltyAssets); } </code>
Open the report<code>solidity function updateCollateralConfig(CollateralConfig memory _collateralConfig) external onlyOwner { _updateCollateralConfig(_collateralConfig); } function _updateCollateralConfig(CollateralConfig memory _collateralConfig) internal { if (!CoreLogic.validateCollateralConfig(_collateralConfig, PARAM_PRECISION)) revert InvalidCol lateralConfig(); slashingBuffer = _collateralConfig.slashingBuffer; isSlashingBufferAbsoluteValue = _collateralConfig.isSlashingBufferAbsoluteValue; liquidationThreshold = _collateralConfig.liquidationThreshold; collateralFactor = _collateralConfig.collateralFactor; emit CollateralConfigUpdated(_collateralConfig); } </code><br>The above function. is called by owner to update the collateral config, It updates certain important states including liquidationThreshold , the problem is when liquidationThreshold is updated it can trigger sudden liquidation for healthy positions too If we take a look at coreLogic.startLiquidation() it has a check and when liquidationThreshold is changed the check can be bypassed suddenly and healthy positions can become unhealthy and become prone to sudden liquidation.<br><code>solidity if (fullRepayTokenAmount < (collateralValue * liquidationThreshold) / paramPrecision) { revert CannotLiquidateHealthyAgent(); } </code>
Open the reportIWETH constant weth = IWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); // need to manually update for each chain//@audit [L] hardcoded address for weth, WETH has diff address on diff chains
Open the report- Hardcoding any address in contract is bad practice.- If we want to change the address in future we can't change it<br><code>IArbSys public constant ARB_SYS = IArbSys(0x0000000000000000000000000000000000000064);</code><br>Add a variable in constructor to set it and add a setter function whenever you want to change the address
Open the reportThere is an unnecessary check of allowance in deposit() which is not at all required. safeTransferFrom() have a built-in check that the user has provided an allowance or not.<br>So <code>require(erc20.allowance(msg.sender, address(this)) >= _amount, "Insufficient allowance");</code><br>This check is unnecessary and of no use. If there was no allowance check and the user did not approve then at that time also it would revert as the user hasn't provided approval. Because of this reason the check is unnecessary<br>
Open the reportThere are 2 variables which check if it is in a pause state or not.<br>Instead of using 2 variables only 1 variable can be used overall to check if the protocol is in pause state or not.<br>It is preferred to use the Pausable library.
Open the reportThe below two functions are used to do the same thing i.e update the provider registry address, one of these functions are redundant and can be removed
Open the reportchangeWithdrawalStatus() function is used to change the withdrawal status but the comment says <code>@notice change status for user deposit. On or off</code> which should be updated to <code>@notice change status for user withdrawal. On or off</code>
Open the reportWrong data passed during event emission in <code>lockTokens()</code> and <code>unlockTokens()</code><br>As we can see during event emission in first parameter it should be <code>_user</code> address instead of <code>msg.sender</code><br>Due to this wrong data gets passed during event emission
Open the reportThe above function doesn't check if the new providerRegistry is a 0 address or not, just like there is a check in updateTokenRegistryContract function for 0 address , there should be a check in the updateProviderRegistryAdress function too Same 0 address check should be included for updateFizzRegistryAdress too
Open the reportThe reports name a repository or files. Open a report to see its scope.
Is this your project? Show this record on your own website. The label states what the record holds, links to this page and updates by itself. It is free and needs no sign-in.
Automatic: the three strongest facts of this project’s own record (its last audit and two more), highlighted. They update with the record. Picking a fact turns Automatic off.
<head><script src="https://widget.trustblock.run/project-widget.min.js" defer></script></head><body><divdata-tb-project-widgetdata-tb-project-slug="argoapp"></div></body>
Paste the code where the label should appear. It uses no API credit and updates by itself.
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