FEY

CertiKFiles named in the report

CertiK reviewed Feyorra (FEY) on 18 Feb 2025: 46 findings, 0 critical; 29 marked fixed, 7 acknowledged, 10 open.

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

Report hosted on skynet.certik.com.

Trustblock does not review code.

Findings46

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

SeverityOpenAcknowledgedFixed
Critical000
High061
Medium018
Low10020
HighIncorrect Value Of Seconds In A DayFixed

The constant `SECONDS_IN_DAY` is initialized to `30`, however, there are `86400` seconds in a day. This is used during interest calculations and staking calculations to determine the amount of interest and time that a user has staked. Since this value is set to 30, it will treat half a minute as a full day so that users will accumulate interest much faster than designed and will be able to close their stake with no penalty after only 2 minutes.

Medium`cross_chain_transfer()` allows to withdraw lockeFixed

Jetton-wallet `status & 1` bit set means the user can not send jettons. However, `cross_chain_transfer()` ignores the status and allows sending even from locked wallet.

Medium`validateReceiver()` Does Not Ensure Only BridgeFixed

The modifier `validateReceiver()` is used to validate the input `_receiverBridge`, which should be the corresponding `FeyorraBridge` deployed on the destination chain. However, the modifier only checks that this is not the zero address. If the input `_receiverBridge` does not correspond to a deployment of the `FeyorraBridge`, it will call `ccipReceive()` on the wrong address and will not provide any `FEY` tokens on the destination chain. Furthermore, if `transferFeyorraRequest()` is called for a custom chain, then the input `_receiverBridge` is only checked to have nonzero length. Similarly to above, this should be the corresponding `FeyorraBridge` deployed on the destination chain.

MediumExtraArgs Is Hardcoded Causing Potential IncompatFixed

According to Chainlink's best practices https://docs.chain.link/ccip/best-practices#using-extraargs, they suggest constructing `extraArgs` off chain in order to be compatible with future CCIP upgrades. However, instead of taking an input such as `bytes memory extraargs`, the code takes an input `_gasLimit` and constructs the `extraArgs` via the code: ```solidity extraArgs: Client._argsToBytes( Client.EVMExtraArgsV1({gasLimit: _gasLimit}) ) ``` As such the code may not be compatible with future CCIP upgrades. In particular, this does not allow supporting out of order execution.

MediumLack of gas to process bounced `op::cross_chain_tFixed

```c=99 () check_amount_is_enough_to_cross_chain_transfer(int msg_value) impure inline { int jetton_wallet_gas_consumption = get_precompiled_gas_consumption(); int send_cross_chain_transfer_gas_consumption = null?(jetton_wallet_gas_consumption) ? SEND_CROSS_CHAIN_TRANSFER_GAS_CONSUMPTION : jetton_wallet_gas_consumption; throw_unless(error::not_enough_gas, msg_value > get_forward_fee(MY_WORKCHAIN, CROSS_CHAIN_TRANSFER_NOTIFICATION_BITS, CROSS_CHAIN_TRANSFER_NOTIFICATION_CELLS) + get_compute_fee(MY_WORKCHAIN, send_cross_chain_transfer_gas_consumption) + get_compute_fee(MY_WORKCHAIN, RECEIVE_CROSS_CHAIN_TRANSFER_GAS_CONSUMPTION)); } ``` The code expects `msg_value` for `op::cross_chain_transfer` to be at least 2658000 + 6446000 + 8386000 = 17490000 = 0.01749 toncoins. However, in this case if the message `op::cross_chain_transfer_notification` will be bounced by jetton-minter due to lack of `ext_chain_fees`, the bounced message will not be processed by jetton-wallet and the original balance will be lost. The `total_supply` will be left intact.

MediumManual Bridge Transfers Do Not Ensure Same RequesFixed

Chainlinks CCIP router ensures that a request is not executed multiple times. However, the bridge has functionality to manually bridge which does not ensure the same request is not executed multiple times. While only the owner can call `manualExecuteBridgeTransfer()`, the owner may accidentally execute the same request multiple times resulting in a loss of funds for the protocol. Furthermore, if the CCIP router is given a setter function, then it is possible that the new router may not ensure that a request is not executed multiple times. As added protection the smart contract can also ensure the same request is not executed multiple times when bridging via CCIP as well. While this may cause redundant checks, it can reduce the trust assumptions on the CCIP router.

MediumPotential race condition with `last_executed_mintFixed

`op::mint` handler in jetton-minter works this way: 1. `admin_address` sends `op::mint` request with `query_id` equal to `last_executed_mint_query_id + 1` 2. `op::internal_transfer` message is sent to destination jetton wallet 3. `last_executed_mint_query_id` is incremented 4. In case the `op::internal_transfer` is bounced, the `last_executed_mint_query_id` is decremented back However, that opens the risk for race condition. The previous messages can be bounced when the new messages are already processed.

MediumPotential Repeated `requestId` For Custom ChainsFixed

The `requestId` for a custom chain is generated by calling `generateRandomBytes32()`, which is determined using the `nonce`, `msg.sender`, `block.timestamp`, and `address(this)`. If two bridge contracts on different chains are deployed to the same address, then it is possible for the same `requestId` to be generated on two different chains. This may cause issues in the bridging process.

MediumSingle Point Of Failure With No Rate LimitsFixed

The `FeyorraBridge` utilizes Chainlink's CCIP for the transfer of `data` in order to execute the bridging of their `FEY` tokens. However, it does not utilize the built in functionality to bridge tokens that follow the CCT standard. In particular, it does not include rate limits, so that if the bridge is compromised the amount of tokens that can be lost is the entire balance of the bridge. Similarly, if bridging to a custom chain that is not supported by Chainlink's CCIP, there are no limits on the amount of tokens that can be transfered, so that if the owner is compromised the amount of tokens that can be lost is the entire balance of the bridge.

LowAllowlisted Sender Is Not Mapped With ChainFixed

Allowlisted senders are not mapped with their corresponding source chain. This can cause issues if a sender is only intended to be allowlisted on certain chains and not on others.

Show 10 more
LowCCIP Bridging Assumes All Source Chains Are EVM CFixed

The internal `_ccipReceive()` function makes the following modifier call to verify the source chain and sender ```solidity onlyAllowlistedSource( _any2EvmMessage.sourceChainSelector, abi.decode(_any2EvmMessage.sender, (address)) ) ``` However, the sender is decoded as an EVM address. If the source chain is a non-EVM chain, then this will not give the correct sender address and can result in improper validation. Can you please clarify if the CCIP bridge is designed to only interact with EVM chains or if non-EVM chains are to be supported. If non-EVM chains are to be supported, then we recommend refactoring to properly handle non-EVM chains.

LowCommented Out CodeFixed

The cited lines contain commented code that can be removed from the codebase.

LowContracts May Fail To Resume If Owner Renounce OwFixed

The contract inherits from `Pausable` and `Ownable` at the same time. If the owner of a smart contract renounces ownership while the contract is paused, it means that there will be no one with the necessary permissions to unpause the contract. This could result in a permanent state of pause, effectively freezing all contract functionality that is dependent on the pause state. It's crucial to design smart contracts with secure ownership transfer mechanisms and emergency procedures to prevent such situations.

LowContracts That Revert With Native Token TransfersFixed

The function `excessIfNeeded()` is used to return any excess native tokens sent in the `msg.value` to the `msg.sender`. However, if the call to send the `refundAmount` fails, it will not revert and the `refundAmount` will be left in the contract. Note that the owner can call `withdrawNative()` to recover this amount, so it will not be locked in the contract.

LowFragmented Liquidity Across Multiple ChainsFixed

The `FeyorraBridge` contract requires users to transfer `FEY` when submitting a bridge request and transfers `FEY` from the contract when executing a bridge transfer. As this is the only bridge contract, it seems that all chains will have the `FeyorraBridge` lock/unlock tokens when bridging. As such the `FEYToken` will need to be deployed on each chain and the total amount that can be bridged must be transferred/minted to the bridge. However, the circulating supply of `FEY` is increasing due to the staking mechanism, so that the total supply of `FEY` may not be held by the bridge at a given time. This results in fragmented liquidity which increases operational overhead and complicates liquidity management.

LowFunction Not PausableFixed

In the `FeyorraBridge` contract, the `transferFeyorraRequest()` function is pausable. However, the `transferTokenOwnership()` function which can change the balances cannot be paused.

LowInconsistent Use Of `_msgSender()` And `msg.sendeFixed

Throughout the codebase `_msgSender()` from the contract `Context` is used. While this currently returns `msg.sender`, if in the future this is updated then it will cause an inconsistency for those that use msg.sender vs. `_msgSender()`.

LowLack of external address validationFixed

```c=199 int address_size = ext_address_slice.slice_bits(); throw_unless(error::invalid_external_address, (address_size >= 1) & (address_size <= MAX_EXETRNAL_ADDRESS_SIZE)); ``` `cross_chain_transfer()` in jetton-wallet validates that the external address is limited in size. However, it doesn't validate that there are no references. Adding extra data to `op::cross_chain_transfer` message and attaching minimal gas will make jetton-minter `total_supply` inconsistent. Jettons will be lost.

LowMissing Emit EventsFixed

The following functions update important contract states, however, they do not emit any event. - `updateCustomChain()` - `allowlistDestinationChain()` - `allowlistSourceChain()` - `allowlistSender()` - `withdrawToken()` - `withdrawNative()` - `deleteChain()` - `transferTokenOwnership()`

LowMissing Error MessagesFixed

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.

Show 9 more
LowNo Getter Method For Pending OwnerFixed

When transferring ownership a pending owner is set, who must then accept ownership to become the owner. While `s_owner` has a getter function, `s_pendingOwner` does not. This can make it difficult for a normal user to get transparent information and cause confusion.

LowNo Method To Set Router AddressFixed

While the CCIP router is not expected to change, it may change due to unforeseen circumstances relating to performance or issues with integrations on certain chains. There is no method to change the CCIP routers address in the event that this happens, in which case the protocol would need to be redeployed in order to change the routers address.

LowPossible Loss Of the Data Due to Indexed DynamicFixed

When attempting to index dynamic data types like `string`, `bytes`, `array`, or `struct` in Solidity, they don't get stored in their original form. Instead, the Ethereum log system stores the `Keccak-256` hash of these data types. While this approach ensures efficiency and cost-effectiveness, developers must be aware of it to correctly use and interpret logs, and they cannot retrieve the original string from its hash alone.

LowReturn Variable UtilizationFixed

The linked function declarations contain explicitly named `return` variables that are not used within the function's code block.

LowRisks of `abi.encodePacked()`Fixed

The current implementation of `RequestIdGenerator.getRequestId()` employs `abi.encodePacked()` with multiple dynamic type inputs controlled by users: ```solidity bytes calldata _receiverBridge, bytes calldata _recipient ``` This encoding method introduces malleability, making it susceptible to manipulation as altered inputs could potentially achieve identical output bytes from `abi.encodePacked()`.

LowSame Event Emitted By Both `transferFeyorraRequesFixed

There are two functions `transferFeyorraRequest()`, one intended for non-custom chains which use Chainlink's CCIP, and the other for custom chains where there bridging will be done manually by the project. However, both emit the same event `TransferFeyorraRequest()`. This can cause issues if the bridge uses the emitted events to execute the bridge transfers on the destination chains. In particular, it must be ensured that for custom chains, the emitted events for non-custom chains will not be mistaken for custom bridging. This is of particular concern if a chains custom status is updated.

LowTypos and outdated commentsFixed

There are typos in the code: - "phaes" is supposed to be "phase" (stdlib.fc:L837) - "iternal" is supposed to be "internal" (gas.fc:L86) - "maunal" is supposed to be "manual" (gas.fc:L36) - "externbal" is supposed to be "external" (gas.fc:L60) - "EXETRNAL" is supposed to be "EXTERNAL" (jetton-utils.fc:L4) - "stdlib.fc#L733" is supposed to be "stdlib.fc#L746" (jetton-minter.fc and jetton-wallet.fc) - "burn_notification" comment at jetton-wallet.fc:L205 is outdated - "metadata url (contains snake slice without 0x0 prefix)" is supposed to be "metadata" - minimal fees comments in gas.fc:L14 are outdated - "ampped" is supposed to be "mapped" (Token.sol:L212) - "receipient" should be "recipient" (Token.sol:L16,L59) - "clsoed" should be "closed" (FEYToken.sol:L291) - "atleast" should be "at least" (FEYToken.sol:L319) - _sender comments in `Helper._transferCheck()` L50 is outdated - return comments in `Helper._transferCheck()` L54 is outdated

LowUnnecessary InitializationFixed

In `RandomBytes32Generator`, `nonce` is explicitly initialized to 0. However, it is a `uint256` which has a default value of 0.

LowUnsafe Casting from uint256 to uint128Fixed

The linked code includes casting from `uint256` to `uint128`. While this is fine given the current parameter magnitudes, the subtle nature of this typecasting increases the risk that future changes to the implementation might overlook its impact. If the code is upgraded without properly accounting for this casting, larger variable sizes could result in unintended consequences, such as data truncation or precision loss, potentially introducing new issues.

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

  • contracts/Timing.sol
  • contracts/CCIP/ConfirmedOwnerWithProposal.sol
  • contracts/SafeMath.sol
  • contracts/Declaration.sol
  • contracts/Errors.sol
  • contracts/FeyorraBridge.sol
  • contracts/Ownable.sol
  • contracts/IERC20.sol
  • contracts/CCIP/ConfirmedOwner.sol
  • contracts/FEYToken.sol
  • contracts/Snapshot.sol
  • contracts/CCIP/OwnerIsCreator.sol
  • contracts/CCIP/IOwnable.sol
  • contracts/CCIP/IAny2EVMMessageReceiver.sol
  • contracts/CCIP/IERC165.sol
  • contracts/CCIP/IRouterClient.sol
  • contracts/Migrations.sol
  • contracts/Token.sol
  • contracts/SafeERC20.sol
  • contracts/RandomBytes32Generator.sol
  • contracts/Events.sol
  • contracts/CCIP/CCIPReceiver.sol
  • contracts/CCIP/Client.sol
  • contracts/Pausable.sol
  • contracts/Helper.sol
  • contracts/Context.sol
  • contracts/Address.sol

Part of Feyorra’s security record

Feyorra
Audits1 audit by 1 firmLast audit Feb 2025, by CertiK.
Critical findingsNo open critical findingsIn 1 report.
Open Feyorra’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