June 13, 2022
Bidirectional Rounding: A Common Security Vulnerability in Defi Smart Contracts

Sec3 (formerly Soteria) has a team of top experts in security audits of smart contracts. In the last few months, we’ve observed an increasing number of vulnerabilities in DeFi applications that fall into a common pattern: bidirectional rounding.

If a smart contract has a bidirectional function or functions (e.g., swap between a pair of tokens or mint/redeem a token) and the function uses the same rounding operation over arithmetic results in both directions, then the function is likely vulnerable to two-way trading attacks.

In this article, we explain the key issues of bidirectional rounding and show how Sec3 Pro automatically detects this type of rounding vulnerabilities.

Why bidirectional rounding is vulnerable?

The reason is simple: bidirectional rounding may allow attackers to exploit round-off errors to gain profit for free through round-trip trading:

1. trade X token1 for token2 => get Y token2

2. trade Y token2 for token1 => get X’ token1

3. profit == X’-X > 0

Although profit is small in each round-trip trade (steps 1–2), the round-trip may be repeated many times, eventually leading to significant losses to the protocol.

How bidirectional rounding looks like?

Let’s consider a real-world example of bidirectional rounding in the spl token-lending program (discovered by Neodyme originally, see “How to Become a Millionaire, 0.000001 BTC at a Time”):

line 65: deposit_liquidity calls liquidity_to_collateral
line 77: redeem_collateral calls collateral_to_liquidity

The functions liquidity_to_collateral and collateral_to_liquidity are a pair of bidirectional functions, called by deposit_liquidity and redeem_collateralrespectively.

In each direction, given an input collateral_amount or liquidity_amount, the functions compute liquidity_amount or collateral_amount.

However, in both directions, the functions use the same rounding operation try_round_u64 internally:

line 558: collateral_to_liquidity calls try_round_u64
line 571: collateral_to_liquidity calls try_round_u64

Since rounding up or down can be controlled by the attacker through the inputs collateral_amount and liquidity_amount, a two-way swap can be made profitable.

How to avoid vulnerability?

The fixes to bidirectional rounding depend on the semantics of the code, but in most common cases can be simple:


The fixes above (i.e., adding to a floor() operation to amount_out and a ceil() operation to fee) ensure that amount_out is always rounded down and fee is always rounded up, thus a round-trip trade is no longer profitable.

For example, the fixes in spl token-lending apply a try_floor_u64() operation to ensure the result is always rounded down:

bidirectional rounding error fix in collateral_to_liquidity
bidirectional rounding error fix in liquidity_to_collateral

Note: bidirectional rounding vulnerabilities do not necessarily need two different functions, but can exist in a single function that handles two-way trading, such as swap.

How does Sec3 Pro detect it automatically?

Sec3 Pro is the premier security analysis service for Solana smart contracts.

To detect bidirectional rounding issues:

The tool first finds out all the bidirectional functions in the whole smart smart including dependencies. For each bidirectional function, it locates rounding signatures (e.g., often Integer multiplications followed by division) and if the result of any rounding operation is not consistently rounded up or down, then the tool flags a potential vulnerability.

The following shows a screenshot of bidirectional rounding reported by Sec3 Pro in our example:

Sec3 Pro is available at https://pro.sec3.dev, including a free plan for the Solana ecosystem.

About Sec3 (formerly Soteria)

Sec3 is founded by leading minds in the fields of blockchain security and software verification. Sec3’s mission is to create a decentralized future that is secure. Sec3 team is currently building a trustworthy platform for securing Solana projects.