How Not to Design Anchor Programs: The Fee Trap
What seems harmless on Ethereum can cost you thousands on Solana — here’s why
Table of Contents
Intro: A Simple Prediction Market?
Consider that you are writing a prediction market with a create_market
function that is permissionless, and another function, settle_market
, that is permissioned and can only be called by the owner. Traders participate in these prediction markets, and a portion of the fee is paid to the market creator, with the remainder going to the protocol owner. It sounds simple, but developers moving from an EVM-style coding approach to Anchor often make a common mistake...
The EVM Design
First, let’s design how the contracts might look in Solidity and Anchor. In Solidity, we would create the market something like the following: the user passes in the question and end time, and we create a market by deriving the ID and minting the YES and NO tokens to the market creator.
(Yes, I caught that DoS attack vector while auditing this codebase too — but let’s pretend it doesn’t exist for a second.)
Very simple so far — once the market has been created, traders interact with it by calling mintDecisionTokens
. After the end time has passed, a cron job calls the settleMarket
function, which closes the market, selects the winning token, and allows holders of the winning token to redeem their tokens for a profit.
Let’s look at the fee part. The fee is stored with a precision of 4 — which simply means that 10000 = 100%
and 100 = 1%
. When a user trades in the market, a certain percentage is paid to the creator of the market, and the remaining goes to the admin, which could be either a multisig or an EOA. The code would look like the following:
The key focus area is how we can transfer the fee in ERC20 directly to the creator without running into any problems or edge cases.
Recreating It in Anchor
Now, let’s implement the same thing in Anchor and see what problem arises that could turn this design into a profitable attack.
We’ll assume the market has been correctly created. The next step is calling mint_decision_tokens
, for which the following accounts are passed in:
A lot to unpack here, but the only line relevant for our current discussion is line 95, where we pass in the market_creator_collateral_token_account
.
Notice that we use init_if_needed
— this is necessary to prevent a Denial of Service attack by front-running the token account creation.
The intuition here is that as soon as a trader mints the YES or NO token by paying the collateral token, a portion of it is directly transferred to the creator’s token account. This is implemented in the code as follows:
Pretty straightforward, right? At least, it seems like it...
The Hidden Bug
Every account on Solana pays some rent to exist, and accounts can be closed to reclaim that rent.
Currently, the rent for a token account is 0.002 SOL, or approximately $0.33 at the current price of $144 per SOL.
In our flow, a single creator sets up a prediction market. Let’s say the market question is: “Will Bitcoin hit $150,000 per coin by the end of 2026?” — and it becomes a very popular market.
By using init_if_needed
for the creator's collateral token account, we are forcing each participant to create that token account if it doesn’t already exist — and the rent is paid by the buyer.
Now, assume there are a total of 5,000 participants in this market (could be fewer, could be more). Since market creation is permissionless, here’s what a malicious market creator could do:
Consider the following scenario:
Trader 1 is ALICE
ALICE mints YES tokens for $1000.Token account creation triggered
Since the creator’s collateral token account doesn’t exist, it is created usinginit_if_needed
. The rent (~$0.33) is paid by ALICE (the buyer).Creator exploits the system
The creator sees the token account has been created and closes it immediately, reclaiming the rent.Why this works
Even though the buyer pays for account creation, the creator is the owner of the token account and can close it at any time.Profit per participant
Each closure gives the creator ~$0.33 in reclaimed rent.Scaling the attack
If the market has 5,000 participants, the creator can repeat this for every trade:
Attack Profitability
5,000 x $0.33 = $1,650
All passively extracted from users without their knowledge.
Redesigning the Fee Flow
The pattern we used above is a push pattern, where we are pushing the fee to the creator. Instead, we need to use a pull pattern, where the creator can pull their accrued fees.
We need to create a treasury first — basically a token account derived from seeds using the creator’s key, so each creator has their own treasury account, owned and controlled by the program. From this account, they can claim their fees by calling the claim_creator_fee
function.
The first change we need to make is: when the creator calls the create_prediction_market
function, we create the treasury account in the same transaction by adding the following to the accounts.
Now, the next step is to remove the token account that was being created for the creator by the buyer in the mint_decision_tokens
transaction.
We replace the market_creator_collateral_token_account
with the creator_fee_treasury
.
Note that this is passed as a mutable account — we are not initializing it here.
Also, the attack involving closing the account is no longer possible, since it is a program-owned account.
Now, we can simply transfer the fee to this account, which can later be claimed using the following entry point. The creator calls the claim_creator_fee
function, and the amount is transferred to them using signer_seeds
.
Final Thoughts
With the wave of Ethereum developers now onboarding to the Solana ecosystem — and many recent examples of Ethereum projects building their Solana variants (e.g., Time.Fun migrating from Base to Solana, GMX Exchange building their Solana programs) — it’s more important than ever to deeply understand how Solana's model differs from Ethereum’s.
Components that work fine on Ethereum can easily introduce critical bugs when translated into Anchor programs, unless these fundamental differences are properly accounted for.
More Reads
If you enjoyed this post, you might also like:
A Hitchhiker’s Guide to Advanced Solana — deep dive into Solana internals and runtime design patterns.
Understanding Bonding Curves in Solana — a breakdown of bonding curve mechanics in a live DeFi system.