Two different bugs in a fork of the Gains Network leveraged trading protocol could have allowed traders to profit 900% on every trade, regardless of the price of the token traded, according to an April 19 report from blockchain security firm Zellic. One of the bugs existed in a previous version of Gains but was later patched. The other was only found in a fork of the protocol.
According to Zellic, its staff informed the developers of Gains forks Gambit Trade, Holdstation Exchange, and Krav Trade of the vulnerability, and these development teams have ensured their protocols do not contain such two flaws. However, other Gains forks may still be vulnerable, Zellic warned.
According to its official website, Gains Network is an ecosystem of decentralized finance (DeFi) products on Polygon and Arbitrum. The official name for its leveraged trading app is “gTrade.” It has facilitated over $25 billion in derivatives volume since its inception in May 2023, according to blockchain analytics platform DefiLlama.
Zellic claimed that several popular DeFi trading apps are derived from Gains Network’s base code, including the aforementioned Gambit Trade and Holdstation, as well as many other protocols. They discovered the exploit while studying a particular fork but declined to name which one they discovered it in.
According to the report, Gains Network contracts allow users to open either a market, reversal or momentum trade order. A market order buys or sells an asset immediately, regardless of price.
When a user asks to open a momentum or reversal trade, the smart contract records an “order” that contains data about what price the user is willing to trade at. Once this price is reached, any user can call the executeLimitOrder function to fill the order. The user who calls the execution does not have to be the same one who placed the order. Users who call the execution get paid a small “execution fee” for performing this role.
This allows users to place limit (momentum) and stop-limit (reversal) orders similar to the way they can in a centralized exchange, but without needing a centralized entity to perform the order fills.
When a user places an order, they can set a take-profit price, stop-loss price or both. The intention of this design is to allow traders to automatically exit a profitable trade at the take-profit point or a losing trade at the stop-loss point.
Bug in Gains fork allowed 900% profit on buy orders
In the Gains fork it studied, Zellic discovered that when an order was opened, the stop-loss price became stored in the “currentPrice” variable used to calculate profit and loss. This meant that if a user was able to set the stop-loss above the open price, they could automatically profit from any trade.
For example, consider a scenario where the price of Bitcoin (BTC) was $63,000 and the user entered $62,000 as the open price and $64,000 as the stop-loss. In this case, if the price fell to $62,000, the order would become filled. But the price would be immediately below its stop-loss, triggering an automatic exit.
In addition, the stop-loss set by the user would be recorded as the current price. This meant that the user would profit $2,000, even though the correct profit should have been approximately $0. This could have allowed an attacker to profit from every trade and eventually drain the protocol of all of its funds.
To prevent this exploit, the protocol contained a check that threw a “wrong_sl” error if the user attempted to set their stop-loss above their open price on a buy order.
However, the investigators discovered that this check could be bypassed in certain circumstances.
When a user first opened an order, they set the price at which they wanted to open the trade, which was then recorded in the variable “openPrice.” It was at this point that the check was performed. However, the function used to execute an order changed this variable to the value of “a.Price,” which was the current price plus the price impact from the order being opened.
This meant that if the user entered an extremely high open price, an executor could bypass the check by simply executing the order. This also allowed the executor to fill the order at an open price below what it was originally set to.
As an example, Zellic considered the idea of an attacker who places an order to buy a token at $100000e10 ($1 quadrillion) and sets a stop-loss at $1 less than this or $999.999999999999 trillion. Once the order is placed, the attacker then executes their own order, causing openPrice to change from $100000e10 to whatever the current price is after the trade’s price impact is taken into account.
The trade then executes and becomes open. As long as the resulting opening price is below the stop-loss that was originally set, it can now be closed by executing the stop-loss. When the attacker executes their own stop-loss, they profits from the difference between the closing price and the price of the stop-loss.
The trade would have resulted in a 900% profit for the attacker, Zellic claimed.
This flaw was not present in Gains Network at the time the Zellic team discovered it. It only existed in the forked version it was investigating. However, in the process of studying this issue, it ran across a second flaw that was present in an earlier version of Gains itself.
Second bug allowed 900% profit on sell orders
The second bug allowed traders to profit 900% on sell orders regardless of price action.
When a trade was closed in the Gains fork, it converted the user’s stop-loss or take-profit point into a variable called “int,” which it then used to calculate profit in percentage terms. But if a user entered a stop-loss or take-profit value that was exactly 2^256-1, the resulting calculations would have caused “int” to become negative.
This was because 2^256-1 is the max value for positive numbers in Ethereum, causing any value above it to “overflow” or start over at zero and because the calculation added the open price to its total. In the Solidity programming language, 2^256-1 is also known as “type(uint256).max.”
According to Zellic, as long as an attacker used leverage of greater than 9x, they could profit 900% from this exploit:
“Let’s consider a sell order, with currentPrice as type(uint256).max. The resultant value of diff would be openPrice + 1 (int(type(uint256).max) = -1 ), and hence the profit percent would be nearly equal to 100 * leverage. Therefore, if the leverage is greater than 9, the function will return the profit as 900%.”
There was a check in the contract that attempted to prevent 2^256-1 from being entered as a take-profit. However, this check was only performed at the moment the order was first opened. If the user changed their take-profit point after the order was opened, they could bypass the check and enter 2^256-1 as the take-profit, allowing them to gain an automatic 900% profit each time they traded.
This second flaw did exist in a previous version of Gains but was subsequently patched. The current version does not contain this flaw, as it performs the check when the take-profit and stop-loss are updated as well as when they are first set.
Zellic reportedly informed all of the above forks about these two security flaws and contacted the Crypto Security Alliance in an attempt to find other protocols that may be affected by them. However, it warned that some Gains forks may still contain these bugs, putting users’ funds at risk of being drained.
Cointelegraph contacted Gains Network, Gambit Trade, Holdstation Exchange, and Krav Trade for comment but did not receive a response by the time of publication.
Gains Network claims that it provides the “real spot price” of listed assets, as opposed to what it sees as less accurate prices based on perpetual contracts. It also claims to offer superior forex trading compared to competitors.
Related: Libra-related Sui blockchain fixes critical bug that put ‘billions’ at risk