Spectrum Protocol - Audit

CertiKFiles named in the report

CertiK reviewed Kroma (Spectrum Protocol - Audit) on 3 Apr 2024: 16 findings, 0 critical; 10 marked fixed, 4 acknowledged, 2 open.

No open critical findings
Open the reportOpen Kroma’s security record

Report hosted on skynet.certik.com.

Trustblock does not review code.

Findings16

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

SeverityOpenAcknowledgedFixed
Critical000
High020
Medium023
Low207
MediumCalling the function `setIndexEvalRate()` fails iFixed

In the function `setIndexEvalRate()`, if the `evalRate` is not zero, the function will revert. This means the function only sets the `evalRate` to 0. However, according to the function `setIndexToken()`, if the `evalRate` is set to 0, the `idxAddrs` should be removed from `idxAssetList` and `feeConfigs` should be deleted. ```solidity=158 function setIndexEvalRate(address[] memory idxAddrs, Ubp[] memory evalRates) public onlyAdmin { SpectrumCoreStorage storage $ = _getSpectrumCoreStorage(); if (idxAddrs.length != evalRates.length) revert Spectrum(SpectrumErrors.Length); for (uint256 i; i < idxAddrs.length; i++) { if (!evalRates[i].isZero()) revert Spectrum(SpectrumErrors.RateZero); $.idxAssetConfigs[idxAddrs[i]].evalRate = evalRates[i]; } } ```

MediumIncorrect token approveFixed

In the function `_stakeReceiveFrom()`, if the `requestedAsset` is `address(0)`, `idxAsset` is assigned the value of `$.underlyingToIdx[$.info.fundUnderlying]`, the function `wrap()` transfers `fundUnderlying` to the `idxAsset` contract while minting `idxAsset` tokens to the contract. Therefore, the contract should increase the allowance of `fundUnderlying` token instead of `idxAsset` token for the `idxAsset` contract. Additionally, the user does not transfer any tokens to the contract. ```solidity=153 if (requestedAsset == address(0)) { idxAsset = $.underlyingToIdx[$.info.fundUnderlying]; IEcoERC20(idxAsset).safeIncreaseAllowance(idxAsset, amount); idxAmount = IidxAssetWrapper(idxAsset).wrap(amount); } ```

MediumPotentially, `idxAsset` and `idxAmount` may be ovFixed

In the function `_stakeReceiveFrom()`, if the `requestedAsset` is `address(0)`, `idxAsset` is assigned the value of `$.underlyingToIdx[$.info.fundUnderlying]` and `idxAmount` is assigned the value of the `wrap` function of the `idxAsset`'. However, the function is not terminated and returned. Therefore, `idxAsset` may be overridden with `address(0)` if the `$.underlyingToIdx[requestedAsset]` returns `address(0)`. Once the `$.idxAssetList` does not contain `address(0)`, the function will revert. ```solidity=146 function _stakeReceiveFrom( address requestedAsset, address from, uint256 amount ) internal returns (address idxAsset, uint256 idxAmount) { SpectrumCoreStorage storage $ = _getSpectrumCoreStorage(); if (requestedAsset == address(0)) { idxAsset = $.underlyingToIdx[$.info.fundUnderlying]; IEcoERC20(idxAsset).safeIncreaseAllowance(idxAsset, amount); idxAmount = IidxAssetWrapper(idxAsset).wrap(amount); } idxAsset = $.underlyingToIdx[requestedAsset]; if (idxAsset == address(0)) { idxAsset = requestedAsset; idxAmount = amount; if (!$.idxAssetList.contains(idxAsset)) revert Spectrum(SpectrumErrors.NotExist); IEcoERC20(idxAsset).safeTransferFrom(from, address(this), idxAmount); } else { // should wrap IEcoERC20(requestedAsset).safeTransferFrom(from, address(this), amount); IEcoERC20(requestedAsset).safeIncreaseAllowance(idxAsset, amount); idxAmount = IidxAssetWrapper(idxAsset).wrap(amount); } } ```

LowIncompatibility with Deflationary TokensFixed

When transferring deflationary ERC20 tokens, the input amount may not be equal to the received amount due to the charged transaction fee. For example, if a user sends 100 deflationary tokens (with a 10% transaction fee), only 90 tokens actually arrived to the contract. However, a failure to discount such fees may allow the same user to withdraw 100 tokens from the contract, which causes the contract to lose 10 tokens in such a transaction.

LowInefficient Memory ParameterFixed

One or more parameters with `memory` data location are never modified in their functions and those functions are never called internally within the contract. Thus, their data location can be changed to `calldata` to avoid gas consumption copying from `calldata` to memory. ```solidity=13 function functionCallWithValue( ``` `functionCallWithValue` has memory location parameters: `data`. --- ```solidity=25 function functionDelegateCall(address target, bytes memory data) external payable returns (bytes memory); ``` `functionDelegateCall` has memory location parameters: `data`.

LowInit Functions Are Susceptible To Front-runningFixed

The `initialize()` functions below are not called by another contract atomically after the contract is deployed, so it's possible for a malicious user to call `initialize()` which, if it's noticed in time, would require the project to re-deploy the contract in order to properly initialize.

LowMissing Zero Address ValidationFixed

Addresses are not validated before assignment or external calls, potentially allowing the use of zero addresses and leading to unexpected behavior or vulnerabilities. For example, transferring tokens to a zero address can result in a permanent loss of those tokens. ```solidity=74 (success, data) = to.call{ value: value }(data); ``` - `to` is not zero-checked before being used.

LowPotential Underflow ErrorFixed

In the function `checkAscendingAddresses()`, if the `addresses` array is empty, it would cause an underflow error. ```solidity=168 function checkAscendingAddresses(address[] memory addresses) public pure { unchecked { uint256 len = addresses.length - 1; for (uint256 i; i < len; i++) { if (!(addresses[i] < addresses[i + 1])) revert Spectrum(SpectrumErrors.Sort); } } } ```

LowTest Code should be RemovedFixed

The contracts `TestProxyAdminFail` and `TestEcoOwnable` are named with a prefix of `Test`, which suggests that they may be test codes.

LowUnsafe Integer CastFixed

Type casting refers to changing an variable of one data type into another. The code contains an unsafe cast between integer types, which may result in unexpected truncation or sign flipping of the value. ```solidity=32 return Ubp.wrap(uint32(Ibp.unwrap(bp))); ``` Casted expression `Ibp.unwrap(bp)` has estimated range [-2147483648, 2147483647] but target type `uint32` has range [0, 4294967295]. --- ```solidity=105 return Ibp.wrap(int32(uint32(underlying))); ``` Casted expression `uint32(underlying)` has estimated range [0, 4294967295] but target type `int32` has range [-2147483648, 2147483647]. --- ```solidity=105 return Ibp.wrap(int32(uint32(underlying))); ``` Casted expression `underlying` has estimated range [0, 115792089237316195423570985008687907853269984665640564039457584007913129639935] but target type `uint32` has range [0, 4294967295]. --- ```solidity=109 return Ibp.wrap(int32(underlying)); ``` Casted expression `underlying` has estimated range [-57896044618658097711785492504343953926634992332820282019728792003956564819968, 57896044618658097711785492504343953926634992332820282019728792003956564819967] but target type `int32` has range [-2147483648, 2147483647]. --- ```solidity=117 return int32(Ubp.unwrap(bp)); ``` Casted expression `Ubp.unwrap(bp)` has estimated range [0, 4294967295] but target type `int32` has range [-2147483648, 2147483647]. --- ```solidity=146 return (unwrap(a) * int256(b)) / IbpBase; ``` Casted expression `b` has estimated range [0, 115792089237316195423570985008687907853269984665640564039457584007913129639935] but target type `int256` has range [-57896044618658097711785492504343953926634992332820282019728792003956564819968, 57896044618658097711785492504343953926634992332820282019728792003956564819967].

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

  • contracts/proxy/transparent.sol
  • contracts/access/CallOrder.sol
  • contracts/token/ERC20/ERC20MintableUpgradeable.sol
  • contracts/proxy/admin.sol
  • contracts/access/SelectorRoleControlUpgradeable.sol
  • contracts/spectrum/core/SpectrumCoreLogic.sol
  • contracts/spectrum/core/SpectrumCoreUtils.sol
  • contracts/access/EcoOwnable.sol
  • contracts/token/ERC20/ERC20L2BridgedUpgradeable.sol
  • contracts/token/ERC20/WETH.sol
  • contracts/spectrum/core/SpectrumCoreEntry.sol
  • contracts/token/ERC20/EcoERC20Upgradeable.sol
  • contracts/spectrum/core/SpectrumCore.sol
  • contracts/access/SlotOrder.sol
  • contracts/utils/BasisPointRateMath.sol
  • contracts/spectrum/core/SpectrumCoreState.sol
  • contracts/spectrum/SpectrumETH.sol
  • contracts/token/ERC20/ERC20MetadataUpgradeable.sol

Part of Kroma’s security record

Kroma
Audits3 audits by 1 firmLast audit Dec 2024, by CertiK.
Critical findingsNo open critical findingsAcross 3 reports.
Open Kroma’s security record

Security firm

CertiK

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