The UNFETTERED security record

Ethereum, Polygon (POS)theunfettered.ioGitHub

As of 3 Nov 2026, The UNFETTERED has 1 audit report on Trustblock, by CertiK, dated 22 May 2023. That report lists no open critical findings.

Critical findings
No open critical findings
In 1 report.
Open the latest report
Your label, live
Get the label for your site

Record last updated . It holds audits and findings, contracts and incidents up to Nov 2026. Trustblock does not review code.

Audits1

Newest first

AuditSecurity firmDateScopeCritical findingsReport
The UnfetteredSecurity firm: CertiKDate: Scope: Files named in the reportNo open critical findingsOpen · skynet.certik.com

Findings29

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

SeverityOpenAcknowledgedFixed
Critical000
High020
Medium000
Low10017
LowCostly Operation Inside LoopThe Unfettered · CertiK · 22 May 2023Fixed

Accessing storage variables in a loop can be costly in terms of gas consumption. ```solidity=404 totalRewardAmount -= tokenRewards[_rewardOwner][i].amount; ``` --- ```solidity=430 totalRewardAmount += tokenRewards[_rewardOwner][i].amount; ```

Open the report
LowDangerous Usage of `tx.origin`The Unfettered · CertiK · 22 May 2023Fixed

`tx.origin` is widely discouraged as there are possible phishing attempts. For a contract, the `tx.origin` is not transparent. In a simple call chain A->B->C->D, inside D msg.sender will be C, and tx.origin will be A. If the origin is really desired in D, then each of the functions in the contracts B, C, D could take an extra parameter to propagate the origin: A would pass its address (this) to B, B would pass the value to C, and C would pass it to D. `tx.origin` based protection can be abused by a malicious contract if a legitimate user interacts with the malicious contract. Hence it's not recommended to use `tx.origin` for authorization in solidity documentation. If one of the `manager` roles calls a phishing contract and then the contract calls the `transferFrom` of the `SoulsToken`, the bot prevention mechanism will be invalid. Refer to: https://docs.soliditylang.org/en/v0.7.0/security-considerations.html#tx-origin

Open the report
LowEnough rewards in `staking` should be guaranteedThe Unfettered · CertiK · 22 May 2023Fixed

It is important to ensure that there are sufficient rewards in the `staking` contract to avoid a situation where stakers may not be able to withdraw their rewards. Additionally, it is worth noting that the `withdrawStake` function withdraws all the staking tokens and rewards, which could result in the inability to withdraw staking tokens if there are insufficient rewards.

Open the report
LowExplanation on the process of approvalThe Unfettered · CertiK · 22 May 2023Fixed

Based on the logic of the `isApproved` function, if the number of affirmative approval reaches three, the topic will be considered approved. ```solidity=182 function changeManager1Address(address _newAddress) external onlyManager(msg.sender) { ... bool _isApproved = isApproved(_title, _encodedValues); if (_isApproved) { manager1 = _newAddress; _deleteTopic(string.concat("Managers: ", _title)); } ... } ``` We understand all managers call the function `changeManager1Address` until the approval reaches three, the manager's address will get changed. However, in a specific scenario, there are four managers who have the ability to use the `changeManager1Address` function to modify a manager's address. If only one manager utilizes this function, the manager's address will not be modified because only one approval for the change topic has been received at that point. If the other three managers choose to approve the topic of changing the manager's address by calling the `approveTopic` function instead of using the `changeManager1Address` function, the number of approvals will reach three. Despite this, the manager's address (L196: `manager1 = _newAddress;`) will remain unchanged because there are no additional managers who have the ability to call the `changeManager1Address` function to carry out the change since each manager can only approve a topic once (as seen in `require(!managerApprovalsForTopic[_title][tx.origin].approved, "Already voted");`). ```solidity=324 function _approveTopic(string memory _title, bytes memory _encodedValues) private { ... require(!managerApprovalsForTopic[_title][tx.origin].approved, "Already voted"); ... } ``` We would like to confirm with the client if the current implementation aligns with the original project design.

Open the report
LowGrant a max amount of tokens to the router addreThe Unfettered · CertiK · 22 May 2023Fixed

The below code segment approves the Router contract to transfer a max amount of tokens on behalf of the owner of the tokens. This can be a security risk if the Router contract is compromised or if the owner of the tokens did not intend to approve a max amount. ````solidity IERC20(soulsTokenAddress).approve(address(routerAddress), type(uint256).max); IERC20(stableTokenAddress).approve(address(routerAddress), type(uint256).max); ```` What needs to be reminded is that if the update here is for a specific quantity, then in the `addLiquidityOnDEX` method, re-approval is required.

Open the report
LowLogical issue of `addToBlacklist()`The Unfettered · CertiK · 22 May 2023Fixed

According to the following code: ```solidity function addToBlacklist(address _rewardOwner, string calldata _description) external onlyManager { ... for (uint256 i = 0; i < tokenRewards[_rewardOwner].length; i++) { if (tokenRewards[_rewardOwner][i].releaseDate > block.timestamp) { _remainingAmount += tokenRewards[_rewardOwner][i].amount; totalRewardAmount -= tokenRewards[_rewardOwner][i].amount; } } if (!soulsToken.transfer(crowdfundingVault, _remainingAmount)) { revert TokenTransferError(); ... } ``` The `addToBlacklist()` function records the invoking time as the `blacklistDate` for a `_rewardOwner`. After the `blacklistDate`, the `_rewardOwner` will not be able to claim the corresponding vesting reward. Additionally, all vesting rewards prior to the `blacklistDate` will be transferred from the `Crowdfunding` contract to the `_crowdfundingVault`. This may cause problems as follows: 1. The `_rewardOwner` may still claim all vesting rewards prior to the `blacklistDate`, even if they have been blacklisted. 2. Some of the vestings may have already been transferred to the `_crowdfundingVault` when they are deactivated. As a result, the reward tokens in the `Crowdfunding` contract may not be sufficient for other users to claim.

Open the report
LowMissing Error MessagesThe Unfettered · CertiK · 22 May 2023Fixed

The **require** can be used to check for conditions and throw an exception if the condition is not met. It is better to provide a string message containing details about the error that will be passed back to the caller.

Open the report
LowSource Of Token Used to Add LiquidityThe Unfettered · CertiK · 22 May 2023Fixed

Managers can add extra liquidity on DEX via this function, but we noticed no more stable tokens transferred to the liquidityVault. The stable tokens transferred previously have been used up in the initial liquidity activity.

Open the report
LowState Variable Should Be Declared ConstantThe Unfettered · CertiK · 22 May 2023Fixed

State variables that never change should be declared as `constant` to save gas.

Open the report
LowThe `_activeStakerCount` should be checked not zeThe Unfettered · CertiK · 22 May 2023Fixed

It is recommended to check that the `_activeStakerCount` is not zero before executing the `for` loop.

Open the report
Show 7 more
LowThe calculation in the `fetchStakeRewardForAddresThe Unfettered · CertiK · 22 May 2023Fixed

According to the following code: ```solidity function fetchStakeRewardForAddress( ... _totalAmount = stakes[_address][_stakeIndex].amount + ((stakes[_address][_stakeIndex].amount * stakes[_address][_stakeIndex].percentage * _dateDiff) / (yearToSecond * 100 ether)); ... uint256 actualPenaltyRate = stakes[msg.sender][_stakeIndex].percentage - ((stakes[msg.sender][_stakeIndex].percentage * _dateDiff) / (stakes[msg.sender][_stakeIndex].monthToStake * monthToSecond)); _penaltyAmount = (_totalAmount * actualPenaltyRate) / 100 ether; } } ``` , we would like to confirm whether the unit of `stakes[_address][_stakeIndex].percentage` already includes 1 ether or not, as the value needs to be divided by 100 ether. Furthermore, we have noticed that the `_penaltyAmount` is calculated using `_totalAmount` instead of `stakes[_address][_stakeIndex].amount`, which may result in higher penalties for stakers. We would appreciate it if the team could confirm whether this is intended according to the original design or not.

Open the report
LowThe calculation of `totalStakedAmount`The Unfettered · CertiK · 22 May 2023Fixed

It is recommended to ensure that the `totalStakedAmount` records all the staking token amounts and is not affected by rewards or penalties. However, in the `emergencyWithdrawStake` function, when the penalties exceed the rewards, only the `_amountAfterPenalty` part is deducted but the difference is not deducted from the `totalStakedAmount`. Consequently, even if all staking amounts are withdrawn, the `totalStakedAmount` will not be zero.

Open the report
LowThe redundant `isStaker` and `_thereIsActiveStakeThe Unfettered · CertiK · 22 May 2023Fixed

The `isStaker` and `_thereIsActiveStakeForAccount` seems to serve no purpose in the `staking` contract.

Open the report
LowUnchecked ERC-20 `transfer()`/`transferFrom()` CaThe Unfettered · CertiK · 22 May 2023Fixed

The return value of the transfer()/transferFrom() call is not checked.

Open the report
LowUnused ContractThe Unfettered · CertiK · 22 May 2023Fixed

```solidity=17 using ERC165Checker for address; ``` - The library `ERC165Checker` is declared on a using-for directive in `MainVault` but its functionalities are never used. --- ```solidity=15 using ERC165Checker for address; ``` - The library `ERC165Checker` is declared on a using-for directive in `CrowdfundingVault` but its functionalities are never used.

Open the report
LowUnused Return ValueThe Unfettered · CertiK · 22 May 2023Fixed

The return value of an external call is not stored in a local or state variable.

Open the report
LowVariables That Could Be Declared as ImmutableThe Unfettered · CertiK · 22 May 2023Fixed

The linked variables assigned in the constructor can be declared as `immutable`. Immutable state variables can be assigned during contract creation but will remain constant throughout the lifetime of a deployed contract. A big advantage of immutable variables is that reading them is significantly cheaper than reading from regular state variables since they will not be stored in storage.

Open the report

Contracts

No deployed address is recorded for these audits.

The reports name a repository or files. Open a report to see its scope.

The repositories and files the reports name

Show this on your site

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.

Content

Facts

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.

Fixed as
Language

Style

Layout
Theme
Accent
Corners

Format

Size
Date
Embed as
HTML
<head>
<script src="https://widget.trustblock.run/project-widget.min.js" defer></script>
</head>
<body>
<div
data-tb-project-widget
data-tb-project-slug="the-unfettered"
></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