Film.io security record

Ethereum, Polygon (POS)film.ioGitHub

As of 3 Nov 2026, Film.io has 1 audit report on Trustblock, by CertiK, dated 17 Mar 2023. That report lists no open critical findings.

Critical findings
No open critical findings
In that report, 5 critical findings were reported. Each is marked fixed by the firm that reported it. See the findings
Open the latest report
Your label, live
Get the label for your site

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

Audits1

Newest first

AuditSecurity firmDateScopeCritical findingsReport
FilmioSecurity firm: CertiKDate: Scope: Files named in the report5 critical findings, fixedOpen · skynet.certik.com

Findings33

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

SeverityOpenAcknowledgedFixed
Critical005
High001
Medium011
Low3022
CriticalChecked Case Logic is IncorrectFilmio · CertiK · 17 Mar 2023Fixed

In function `unstaketokens()`, there is an attempt to check that `stakeid` input matches the recorded parameter `stakeid` for a struct corresponding to the `stakeid` in the `stake` arrays `stakeofaccounts` and `objectStakes`. In such a case, the goal is to either delete the corresponding information or update the recorded `quantity` parameter. However, there is no reason that the `stakeid` parameter at `stakeofaccounts[stakeid - 1].stakeid` or `objectStakes[stakeid - 1].stake.id` should be equal to the input `stakeid`, and in most cases where multiple users are staking multiple times and in between one another, this is likely false. This means this check in the if-block conditional statement will cause issues in the function logic. ### Scenario Say 1000 users have already staked so that storage variable `stakeindex` is currently value 1000. Now assume that a user, Alice, wishes to stake for the first time. Previously for Alice, her corresponding `stake` array, `stakeofaccounts = []`, is an empty array. Alice successfully calls `staketokens()` and now `stakeIndex = 1001`, and her `stakeId` will also be `1001`. However, Alice has only staked once, so she has one entry in her array `stakeofaccounts`: a `stake` struct with `stakeId` of 1001. Thus `stakeofaccounts[0].stakeid` is 1001 for Alice, while a check to `stakeofaccounts[stakeid -1].stakeid` is a check of the 1000th entry of this array. This entry does not exist for Alice, and will cause a revert. In this way, if more than one user attempts to stake with this contract, all but the first staker will be kept from unstaking because of the revert caused by this logical issue. The same issue occurs with `objectStakes` logic.

Open the report
CriticalEvaluations can be overwritten by other users witFilmio · CertiK · 17 Mar 2023Fixed

Any rating entry in the mapping `projectEvaluation` can be overwritten with a new chosen rating by any user that has previously created their own evaluation through the logic in `modifyEvaluation()`. The function checks that an input `prj_id` matches the parameter in `projectLockDetails[prj_id]` and `projectEvaluation[prj_id], that `prj_id` is nonzero, that the input `que_id` matches the parameter `projectEvaluation[prj_id].que_id`, and finally that `userEvaluation[_msgSender()].account` is the `_msgSender()`. If two distinct users have used function `createEvaluation()` for two distinct `prj_id` values, then either can update the `projectEvaluation` mapping of the other's `prj_id` to their own chosen `rating` value.

Open the report
CriticalFunction `getlockedAmount()` Does Not Sum `_user`Filmio · CertiK · 17 Mar 2023Fixed

Function `getlockedAmount()` is used to calculate the `lockedAmount` for a sending address in the internal `_transfer()` function of the token. While this function uses the length of `userLocks[_user]` as an upper bound in a for loop, to sum up `lockedAmount`, there are several issues with the logic of this function. 1. The `lockDuration` and `lockAmount` at `lockDetails[i]` are used in the function logic, where `i` is the iteration number of the for-loop. This entry of the mapping `lockDetails` may or may not correspond to the `_user` address, so the resulting `lockedAmount` that is calculated in the for-loop may or may not be equal to the actual total amount locked for the `_user` address. The value can be less or more than `_user` address's actual total locked amount. This is a critical vulnerability, as it may allow users to transfer funds that are meant to be locked, it may keep users from transferring amounts meant to be unlocked, and it may result in a `lockedAmount` that is larger than `balanceOf(from)` in `_transfer()`, causing a revert due to underflow. 2. The for-loop includes the value `userLocks[_user].length` as an iteration value `i` even though `userLocks[_user][length]` is a not an entry of the array `userLocks[_user]`. The function does not revert in this case though, because, again, `lockDetails[i]` is read instead of getting the `lockId` value held at `userLocks[_user][i]`, and `lockDetails` is a mapping while `userLocks[_user]` is an array. These two issues together allow this function to execute inconsistently and cause critical issues with the locking logic of the contract ### Scenario As a simplified example, Alice and Bob are set up with a locked amount of tokens through the `lockUp()` function. Alice is given 10 tokens first, so she gets lockId = 1, and Bob is given two locked amounts over lockId = 2 (say amount is 10), and lockId = 3 (say amount is 11). When Alice calls public view function `getlockedAmount()` to see her total locked amount, the `userLocks[_user].length` is 1, so the for-loop will iterate through i = 0 and i = 1. For i = 0, `lockDetails[0].lockDuration` is 0, so nothing is counted (this will always be true). When i =1, assuming `lockDetails[1].lockDuration` is larger than the current timestamp, then `lockedAmount` is updated to be `lockedDetails[i].lockAmount`, which is 10. It happens that in this case, i = 1 matches Alice's lockId that she owns, so the return value is accurate. Now consider Bob's case. Again, assuming both his locks are still upheld for the current `block.timestamp`, assume he calls the `getlockedAmount()` function with his address. Then his value of `userLocks[_user].length` is 2, and the for-loop will iterate through i = 0, 1, 2. As before, `lockDetails[0]` will not be counted. However, `lockedAmount` will get `lockDetails[1].lockAmount` (amount is 10), and `lockDetails[2].lockAmount` (amount is again 10) added to it. The total returned `lockedAmount` will be 20, even though for Bob, it is supposed to be 21. This is because two of the `lockDetails[i].lockAmount` values (i = 0, and i =1) did not belong to Bob, resulting in a miscalculation.

Open the report
CriticalIf user creates new evaluation, old evaluation inFilmio · CertiK · 17 Mar 2023Fixed

If a user calls `createEvaluation()` more than once on two different `prj_id` inputs, then the first correspondence for `userEvaluation` mapping will be overwritten with the second, losing the first `EvaluateDetails` struct correspondence. However, the `EvaluateDetails` struct in the `projectEvaluation` mapping will persist for the first `prj_id`.

Open the report
CriticalUsers can create a `stakeid` by sending tokens toFilmio · CertiK · 17 Mar 2023Fixed

When users call the function `staketokens()`, they specify an input address `to` which can be any address they choose as long as it is not the one they are making the call from. The `staketokens()` function uses this address to send tokens from the `_msgSender()` to the target `to` address. This creates recorded stake information within the contract that can then be used in the function `unstaketokens()` to transfer tokens from the `escrow_account` to the `_msgSender()`, regardless of where the tokens were first transferred. A user can have a boundless number of their own accounts. So a user can input one of their own addresses for the `to` input, transfer this `quantity` of tokens from their first account to their other owned account, then immediately call `unstaketokens()` to have an equivalent amount of tokens transferred from the `escrow_account` to their original account. Now they own twice as much of the token as they originally did across two accounts. This pattern can be repeated by the attacker until they have drained all available funds from the escrow account.

Open the report
HighInitial Token DistributionFilmio · CertiK · 17 Mar 2023Fixed

All `FAN` tokens are sent to the `_msgSender()` address when initializing the contract. This is a potential centralization risk as the deployer can distribute all `FAN` tokens without the consensus of the community.

Open the report
MediumInconsistent Rating RequirementsFilmio · CertiK · 17 Mar 2023Fixed

Function `createEvaluation()` has the following requirement: ```solidity require(rating>=100 && rating <=2000,"Rating should be between 100 to 2000"); ``` while function `modifyEvaluation()` has the requirement: ```solidity require(rating >= 10 && rating <= 2000,"Rating should be between 10 to 2000"); //rating is between 1 t0 20 ``` Based on the comment made next to the latter requirement, the rating should be scaled down by a factor of 100 to be 1 to 20, meaning that the rating requirement in function `createEvaluation()` is correct, while the rating requirement in `modifyEvaluation()` does not fit accurately reflect the scale. Any updates in `modifyEvaluation()` below the value of 100 may be incorrectly converted to a value of 0.

Open the report
Low`escrow_account` can be made publicFilmio · CertiK · 17 Mar 2023Fixed

Since the `escrow_account` is the funding account address for unstaking in the `StakeV1` contract, the address should be made public, so that users can see the balance of this address and the allowances of the `StakeV1` address for the `escrow_account`. That way, they are able to make an informed decision about whether to add their tokens to the `StakeV1` contract.

Open the report
LowCheck Effect Interaction Pattern ViolatedFilmio · CertiK · 17 Mar 2023Fixed

The order of external call/transfer and storage manipulation must follow the check-effect-interaction pattern. To follow this pattern, each call to `tokenInterface.transferFrom()` should occur at the very end of the function in which it is used.

Open the report
LowComment in check on `userrating` check is InconsiFilmio · CertiK · 17 Mar 2023Fixed

In the function `createRating()` the input `userrating` must be strictly greater than 0 and no more than 50. The comment in the requirement states "Rating needs to be between 1 to 5". Considering the `userrating` is allowed to be as high as 50, it seems that `userrating` is supposed to be scaled by a factor of 10. However, any inputs for `userrating` that are lower than 10 in that case will be read as 0 when the value is scaled down, due to truncating decimals.

Open the report
Show 10 more
LowDiscussion on Staking FunctionalityFilmio · CertiK · 17 Mar 2023Fixed

The contract `StakeV1` has only two functions that users are meant to interact with: `staketokens()` and `unstaketokens()`. Neither function contains logic for giving a user rewards for staking their tokens. Instead, when users call `unstaketokens()`, they are transferred the amount they were supposed to have originally staked, and nothing more.

Open the report
LowFunction `addReview()` can be called more than onFilmio · CertiK · 17 Mar 2023Fixed

Function `addReview()` can be called more than one time, even though `reviewGiven` is already set to true.

Open the report
LowInheriting from a Non-Upgradeable Contract in anFilmio · CertiK · 17 Mar 2023Fixed

Contract `ERC2771Recipient` is not an upgradeable contract and is not set up for the potential of adding extra storage variables. Usually parent contracts include a `__gap` placeholder storage variable to take up a requisite number of storage slots so that in the event new storage variables need to be added to the parent contract, they can be placed in one of the storage slots held by the `__gap` variable. If storage variables are added directly to contract `ERC2771Recipient` without the prior use of a `__gap`, then storage collisions can occur with the other storage variables, within the proxy contract. Likewise, contracts `VariableDataV1` and `FanTokenVariableV1` are not upgradeable contracts as constructed.

Open the report
LowMissing Check `stakeid` is NonzeroFilmio · CertiK · 17 Mar 2023Fixed

While it is not possible for a user address to be associated to a `stakeid` of 0, if a user accidentally enters 0 as input for `stakeid`, the function `unstaketokens()` will revert and the user may not know why.

Open the report
LowMissing Check for `_amount` in `lockUp()`Filmio · CertiK · 17 Mar 2023Fixed

There is no check that `_amount` is nonzero in function `lockUp()`.

Open the report
LowMissing Emit EventsFilmio · CertiK · 17 Mar 2023Fixed

Functions that update state variables and that are controlled by centralization roles should emit relevant events as notifications.

Open the report
LowMissing InheritanceFilmio · CertiK · 17 Mar 2023Fixed

`FilmioVestingWallet` implements the interface `IFilmioVestingWallet`, but does not inherit from it. ```solidity=22 contract FilmioVestingWallet is Context, Initializable { ``` ```solidity=16 interface IFilmioVestingWallet { ```

Open the report
LowMissing Zero Address ValidationFilmio · CertiK · 17 Mar 2023Fixed

The cited address input is missing a check that it is not `address(0)`.

Open the report
LowName of Contract Does Not Match Project NameFilmio · CertiK · 17 Mar 2023Fixed

The project is called `Filmio` but the contract is name `FilmoProjectV1`

Open the report
LowNo check that target address `_to` is not `addresFilmio · CertiK · 17 Mar 2023Fixed

If `_to` is `address(this)` in function `lockUp()`, then this will prevent the further use of function `lockUp()` until the specified time has passed.

Open the report
Show 9 more
LowOnly One User Can create an evaluation for a giveFilmio · CertiK · 17 Mar 2023Fixed

Once a user calls `createEvaluation()` on a specific `prj_id` successfully for the first time, then `projectEvaluation` mapping gets updated for the `prj_id`, including updating the `account` parameter to the current `_msgSender()`. This means that if someone tries to call `createEvaluation()` using the same `prj_id` a second time, then the call will revert because of the requirement: ```solidity require(projectEvaluation[prj_id].account == address(0) && que_id != 0, "Project is already evaluated by user"); ``` Is it intended to only let a project corresponding to a `prj_id` be evaluated once on a first-come-first-serve basis?

Open the report
LowRecommended Use of `IERC20Upgradeable`Filmio · CertiK · 17 Mar 2023Fixed

Use libraries and interfaces specifically designed to be included with OpenZeppelin's upgradeable contracts to ensure compatibility with upgradeable-contract security patterns.

Open the report
LowRedundant `import` StatementsFilmio · CertiK · 17 Mar 2023Fixed

The contract `VestingWallet.sol` is importing `Address.sol` and `Math.sol` from OpenZeppelin. \ `Address` is already imported through the contract `Initializable.sol` and `Math.sol` is never used.

Open the report
LowToo Many DigitsFilmio · CertiK · 17 Mar 2023Fixed

Literals with many digits are difficult to read and review. ```solidity=16 _mint(_msgSender(), 100000000 * (10**decimals())); ```

Open the report
LowUnchecked ERC-20 `transfer()`/`transferFrom()` CaFilmio · CertiK · 17 Mar 2023Fixed

The return value of the transfer()/transferFrom() call is not checked. ```solidity=124 tokenInterface.transferFrom(_msgSender(), to, quantity); ``` --- ```solidity=164 tokenInterface.transferFrom(escrow_account, _msgSender(), quantity); ```

Open the report
LowUnprotected InitializerFilmio · CertiK · 17 Mar 2023Fixed

One or more logic contracts do not protect their initializers. An attacker can call the initializer and assume ownership of the logic contract, whereby she can perform privileged operations that trick unsuspecting users into believing that she is the owner of the upgradeable contract. ```solidity=7 contract FanTokenV1 is FanTokenVariableV1, ERC2771Recipient { ``` - `FanTokenV1` is an upgradeable contract that does not protect its initializer. ```solidity=12 function initialize(address _trustedForwarder) public initializer { ``` - `initialize` is an unprotected initializer function. --- ```solidity=8 contract FilmoProjectV1 is VariableDataV1, ERC2771Recipient { ``` - `FilmoProjectV1` is an upgradeable contract that does not protect its initializer. ```solidity=12 function initialize(address _trustedForwarder) public initializer { ``` - `initialize` is an unprotected initializer function. --- ```solidity=9 contract StakeV1 is ReentrancyGuardUpgradeable, ERC2771Recipient { ``` - `StakeV1` is an upgradeable contract that does not protect its initializer. ```solidity=57 function initialize(address token, address escrowaccount, address _trustedForwarder) public initializer { ``` - `initialize` is an unprotected initializer function.

Open the report
LowUnverified User-Provided InputsFilmio · CertiK · 17 Mar 2023Fixed

The following user-provided input is not verified for potentially important state variables. - For contract `StakeV1`, the user-provided `typeid` is stored without checking that it matches the rest of the `stake` information. A similar issue occurs with the string input `staketype`. - For contract `FilmoProjectV1`, the user-provided `que_id` is stored without checking that this information correctly corresponds to the user's other inputs. - For contract `FilmoProjectV1`, the privileged account `owner` provides uint array `user_id` as input which is stored in a `LockDetails` struct and never used again within the contract. It is unclear how the unsigned integer values are meant to reference users, and what this information is meant to be used on. The concern is that if this information is used by the frontend as a reference to make further decisions in logic, a user can provide any value they want for these inputs, and manipulate the outcome. Moreover, there is no check that these values make sense for the project. For instance, the user can enter any string value they want for `staketype`, which may have nothing to do with the actual stake type. In `FilmoProjectV1`, we speculate that the `user_id` array was originally supposed to be used to check that only specific addresses are allowed to call the functions of this contract, but no such check exists.

Open the report
LowUse of Storage VariablesFilmio · CertiK · 17 Mar 2023Fixed

There are two concerns with the use of storage variables in function `unstaketokens()`. ### Use of `stakedetails` vs Directly Updating `stakeDetails` mapping In line 149, a reference variable, `stakedetails`, is created which directly references the storage variable `stakeDetails[stakeid]`; the use of the qualifier `storage` means that any time `stakedetails` is updated in the function, it actually updates the `stake` struct `stakeDetails[stakeid]` in storage. Despite this, the `stakedetails` reference is only used to read from storage. Instead, a secondary variable `currentstake` is created and located in memory, at line 181, which is then updated and used to directly change `stakeDetails[stakeid]`. This kind of inconsistency makes the code difficult to check. At best, the creation of storage reference `stakedetails` is unnecessary and can just be located in memory, so that a secondary `currentstake` does not need to be created. We recommend reviewing the above and taking steps to streamline the references to storage variables, both to make it easier to assess and to avoid potential inconsistencies in updates. ### Double-Update of `stake` arrays Both `objectStakeDetails[stakedetails.staketype]` and `accountStakeDetails[stakedetails.account]` are held in reference variables, in lines 166 and 167 respectively, as `objectStakes` and `stakeofaccounts`. Again, any changes to these variables update state. In lines 171 and 173, this updates the same information twice, when the if-block condition is met. When the if-block condition is not met, the information for `accountStakeDetail[stakedetails.account]` is updated to be the exact information it already was previously. The same is true of `objectStakeDetails[stakedetails.staketype]` in lines 175 and 177. The same kind of issue occurs in lines 185, 186, 188, and 189. In other words, the secondary updates at lines 173, 177, 186, and 189 are unnecessary.

Open the report
LowVariables That Could Be Declared as ImmutableFilmio · CertiK · 17 Mar 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="filmio"
></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