From 1bc9f24f7cd0d0c1fccc5d265f0d6ca6f658d48e Mon Sep 17 00:00:00 2001 From: Mike MacCana Date: Wed, 30 Sep 2026 23:00:40 +0000 Subject: [PATCH] Adversary: every role holder is also a user A new section before What You Learnt: an operator, manager or admin can also call every public handler, from a wallet the program cannot link to them, so each role's power has to be weighed against what it is worth to someone who also trades. It uses the perps funding rate (test_operator_on_the_lighter_side_earns_only_the_fixed_rate) and the managed fund's limits on its manager (test_add_asset_rejects_unapproved, test_initialize_rejects_excessive_fee) as examples, and points to Offchain Truth for a role that reports a result. The summary gains a matching bullet. Claude-Session: https://claude.ai/code/session_01JGEoAUjMm7Evv69k46eNcn --- .../the-adversary.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/financial-programming-concepts-in-solana/the-adversary.md b/financial-programming-concepts-in-solana/the-adversary.md index 7a0b00d5..db558190 100644 --- a/financial-programming-concepts-in-solana/the-adversary.md +++ b/financial-programming-concepts-in-solana/the-adversary.md @@ -92,6 +92,17 @@ Two more rules for these boundaries: The *Arithmetic of Money* chapter's postcondition checks, such as re-checking K after the transfers land, follow the same rule: check your own state before anyone else's code runs. +## Every Role Holder Is Also a User + +Most programs in this book give someone a role: an operator who creates a market, a manager who runs a fund, an admin who settles an event. The handlers that need that role's signature are only part of what that person can do. They can also call every handler anyone else can call, from their own wallet or from a new one the program has no way to link to them. A program cannot stop its operator trading in their own market, so design as if they will. + +For each role, list what its handlers can change, then ask what that change is worth to someone who also trades, deposits, or bets in the same program. A parameter that is harmless on its own becomes an attack when the person who sets it also holds a position: + +- **The perpetual futures operator.** Funding is paid to the lighter side of open interest out of the liquidity providers' deposits. If Maria could change the funding rate at will, she could open a small position on the lighter side, raise the rate, and close the position a few seconds later. The program fixes the rate when the pool is created and refuses one above `MAX_FUNDING_RATE_PER_SECOND`, and `test_operator_on_the_lighter_side_earns_only_the_fixed_rate` checks that a wallet on the lighter side earns only that rate. +- **The managed fund's manager.** A manager who could add any token and price it from any feed could buy a token they had minted themselves at a price they reported. The fund takes assets only from Victor's list and feeds only from Victor's entries (`test_add_asset_rejects_unapproved`), swaps only through the router fixed at creation with minimums taken from the oracle, and caps the manager's fee and fixes it at creation (`test_initialize_rejects_excessive_fee`). + +Where the role is the one reporting a result, like the betting market's admin settling an event they have bet on, the question is who to trust, which the *Offchain Truth* chapter covers. + ## What You Learnt - Anyone can take the tokens a mistake on a public chain exposes: attacks are automated, anyone can get their transaction ordered around yours, and attacks start at the extremes of a range. @@ -100,6 +111,7 @@ The *Arithmetic of Money* chapter's postcondition checks, such as re-checking K - A true price can still be a manipulated price: time-weight it, act on the confidence interval, cap how much can move per window, and never lend more against a token than it would cost to move its market. - A rounding error in the user's favor gets repeated until the pool is empty, so write a round-trip invariant test to catch it. - Apply checks-effects-interactions at every boundary: your invariants must hold at the end of your own instruction, because the instructions after yours belong to someone else. +- Treat everyone with a role as a user too: for each role, ask what its handlers can change, and what that change is worth to someone who also trades, deposits, or bets in the same program. [^confused-deputy]: Norm Hardy named it in "The Confused Deputy (or why capabilities might have been invented)", ACM SIGOPS Operating Systems Review 22(4), October 1988, at [dl.acm.org/doi/10.1145/54289.871709](https://dl.acm.org/doi/10.1145/54289.871709).