CertiK reviewed Kroma (Spectrum Protocol - Audit) on 3 Apr 2024: 16 findings, 0 critical; 10 marked fixed, 4 acknowledged, 2 open.
Report hosted on skynet.certik.com.
Trustblock does not review code.
Statuses are the security firm’s own, from its report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 0 | 2 | 0 |
| Medium | 0 | 2 | 3 |
| Low | 2 | 0 | 7 |
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]; } } ```
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); } ```
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); } } ```
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.
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`.
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.
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.
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); } } } ```
The contracts `TestProxyAdminFail` and `TestEcoOwnable` are named with a prefix of `Test`, which suggests that they may be test codes.
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].
The report names a repository or files. Open the report to see its scope.
Repository and files the report names (18)
1,044 projects reviewed · 1,375 reports · latest May 2025
See CertiK’s auditsSomething 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