With over 20 games, numerous partners, and annual revenues exceeding $100 million, FortuneJack is a leader in decentralized gaming. At the heart of this ecosystem is JackToken, a gaming utility token designed to empower users with staking, profit-sharing, and game development opportunities. Beyond its role as a currency, JackToken enables FortuneJack to support decentralized game studios and incubators, driving innovation in the gaming industry.
To ensure the security and functionality of its BEP20 JackToken, staking, and vesting contracts, FortuneJack partnered with AuditOne for a comprehensive smart contract audit. These mechanisms are essential to the ecosystem:
- Staking allows users to lock their tokens, supporting network operations and earning rewards in return.
- Vesting ensures tokens are distributed gradually over time, fostering long-term commitment and reducing liquidation risks.
Blockchain technology adds an extra layer of reliability, eliminating single points of failure and unauthorized modifications. However, even the most robust technology requires scrutiny. That’s where AuditOne stepped in—to ensure that these smart contracts upheld the trust of the community.
The DoS Threat: A Hidden Weakness
One of the most alarming vulnerabilities uncovered during the AuditOne review of the JackToken project was a Denial of Service (DoS) threat. This issue had the potential to disrupt the withdrawal mechanism of the staking contract, freezing user funds and undermining trust in the protocol.
Here's how this exploit could unfold and how it was mitigated:
Imagine an attacker who holds a small amount of JackToken but intends to disrupt the entire staking mechanism. Here's how they could exploit the vulnerability:
- Creating Numerous Addresses: The attacker generates multiple addresses using the CREATE2 opcode, a technique that allows the predictable creation of new addresses.
- Distributing Small Stakes: A small amount of JackToken is transferred to each of these addresses.
- Inflating the StakersArray: These addresses are used to repeatedly stake tokens, massively expanding the stakersArray.
When legitimate users attempt to withdraw their staked tokens, the withdrawal function must iterate through the bloated stakersArray. This overload could exceed the gas limit, causing the transaction to fail or revert, effectively freezing user funds.
Root Cause and Vulnerability Breakdown
The issue stemmed not from JackToken’s own smart contracts but from an integration with the Staking20 contract by thirdweb. The root of the problem lay in the absence of a minimum stake requirement, which left the system vulnerable to exploitation. This weakness allowed even minimal token amounts to exponentially expand the stakersArray, potentially crippling the staking mechanism.
The Attacker's Strategy
- Creating Addresses: Using the CREATE2 opcode, the attacker generates numerous new addresses predictably for each deposit.
- Transferring Tokens: A small amount of JackToken is transferred to each newly created address.
- Depositing Tokens: These tokens are deposited into the staking contract using the STAKE function.
- Repeating the Cycle: The attacker repeats this process, causing the stakersArray to grow significantly in size.
Impact on Users
When legitimate users attempt to withdraw their staked tokens, the withdrawal function iterates through the oversized stakersArray. This process can exceed the gas limit, causing transactions to fail or revert, effectively locking user funds.
A Deep Dive into the Mechanics
- Staking20.sol - stake function invoked.
- JackStaking.sol - _stake function is called. It updates the
lastStakeTimes and call the _stake in the Staking20.sol.
- Staking20.sol - called _stake function.
- Check that the stake amount is not zero.
require(_amount != 0, "Staking 0 tokens"); - it checks only that it ≠ 0
- If the current stakers [_stakeMsgSender] has no amountStaked, then new _stakeMsgSender will be pushed into the stakersArray
- Transfer of token will be successfully executed via safeTransferBEP20.
In this situation, regular customers stake their tokens without suspicion. However, while a person tries to withdraw their complete staked quantity, they may encounter issues due to the attacker's movements.
The withdrawal process involves iterating through the _stakersArray. Since the attacker has substantially expanded this array, the operation can also exceed the gas limit, causing the transaction to fail or revert. The severity of this issue depends on the attacker's intent and the sources they allocate to disrupt the protocol.
The Solution: Raising the Stakes
AuditOne recommended implementing a minimum staking amount in the _stake function. By requiring users to stake at least one token (adjusted for token decimals), the protocol effectively blocked attackers from abusing the staking mechanism. This simple yet effective solution ensured that legitimate users could continue to stake and withdraw tokens without interruption.
Other Challenges Faced
While the DoS issue was the most pressing, the audit also identified other areas for improvement:
- A missing check for maxVestingTime in the addVesting function.
- Logic in LastStakeTime that could unnecessarily extend staking periods.
- Unused inherited AccessControl libraries.
- Gas optimization opportunities.
These critical findings, alongside the DoS vulnerability, were carefully resolved to build a more resilient and secure system.
Explore the full analysis of these and other insights in our detailed breakdown video.
Check your Smart Contract for Free: https://services.auditone.io/security-checklist
Book a Meeting with AuditOne: https://calendar.app.google/cgZHGcPmpFP1mPZ18