
Date: July 30, 2026
Chain: Ethereum
Loss: about $9.6K, per SlowMist
Class: time-of-check to time-of-use gap around an untrusted issuance hook
On July 30, 2026, an attacker deployed a SetToken of their own, a basket token on Set Protocol, and gave it a manager hook they wrote. That one role let them run code in the middle of someone else's transaction, at the exact moment Index Coop's ExchangeIssuance contract had already decided how many tokens it was about to hand over and hadn't yet handed them over.
SlowMist put the loss at about $9,600. ExchangeIssuance, a helper contract that turns a single input token into a SetToken in one call, read the token's position sizes, let the attacker's hook run, and then settled using sizes the hook had inflated by about 93.66 times. The contract never locked the SetToken's state between the read and the settlement, so the attacker changed the numbers in between.
Set Protocol v2 turns asset management strategies into ERC-20 tokens called SetTokens, and it's been the base layer for Index Coop's products. Each SetToken holds a basket of components, and for each one it stores a virtual position unit. The real amount of a component behind one SetToken is that virtual unit multiplied by a token-wide value called the positionMultiplier, which lets the protocol adjust every position at once, for example when a streaming fee is charged.
Issuance goes through modules. The BasicIssuanceModule mints SetTokens in exchange for the right amounts of each component, and before it does, it calls a pre-issue hook the SetToken's manager can configure, a feature Set's documentation describes for product-level rules such as maximum amounts or denylists. ExchangeIssuance sits on top of that flow as a convenience. A caller gives it one input token, it works out how much of each component the requested SetTokens need, buys those components, and calls the module to issue.
According to crypto.training's reproduction, ExchangeIssuance lets anyone call issueSetForExactToken for any SetToken registered with Set's controller. There was no allowlist, so a SetToken the caller deployed qualified, and the manager whose hook the module calls could be the caller.
The flaw is a timing problem called time-of-check to time-of-use, or TOCTOU. ExchangeIssuance checks a value, the component units of the SetToken, then hands control to outside code, then uses the value as if it couldn't have changed. Inside issueSetForExactToken the order was read the units, size the swaps, call BasicIssuanceModule.issue, and the module's call to the manager's hook landed between that read and the moment the module pulled components out of ExchangeIssuance with transferFrom.
Picture a cashier who prices your basket, then lets you step behind the counter to adjust the store's price list before taking payment, and finally charges from the list you just edited. SlowMist's summary, via Foresight News, puts the root cause plainly: the ExchangeIssuance contract didn't lock the SetToken's state while processing issuance requests. Two defects compound here, and both are visible in source. Nothing re-validated the component units after the external hook returned, and nothing restricted which SetTokens ExchangeIssuance would accept, so the "manager" running the hook could be the attacker.
Any value a contract reads before calling out to code it doesn't control has to be treated as stale when that call comes back, and a contract that settles on it has to either lock the state or read it again.
The setup came first. The attacker deployed a malicious SetToken with a custom manager pre-issue hook, built to make fake issue and redeem calls against Set's NAV issuance flow. On-chain, the attacker's wallet, 0x0736...c99d, sent five transactions through its own contract, 0x388a...4802, that morning, three in block 25,644,621 at 08:57:23 UTC and two in block 25,644,631 at 08:59:23 UTC, and each of the first three sent 0.05 WETH into ExchangeIssuance. A SetToken named Batch Hooked Set (BHSET), at 0xF7C2...6948, was created inside the first of them, transaction 0x7f45...658b.
The attacker then called issueSetForExactToken on ExchangeIssuance with that SetToken as the target. ExchangeIssuance read the token's component units and sized the issuance. When BasicIssuanceModule.issue called the token's pre-issue hook, the hook ran its fake NAV issue and redeem calls, and those calls inflated the SetToken's positionMultiplier about 93.66 times.
When the hook returned, the module computed real component amounts from the inflated multiplier and pulled them from ExchangeIssuance through transferFrom. ExchangeIssuance held a residual inventory of tokens, and crypto.training's proof of concept shows the drain of that inventory included about 436.70 LINK. The chain agrees: in transaction 0x7f45...658b, ExchangeIssuance sent 441.42 LINK to the BHSET token after buying only 4.71 LINK from a Uniswap V2 pool, and the 436.70 LINK difference came out of its own balance. SlowMist's total for the loss was about $9,600.
The loss stopped at what ExchangeIssuance happened to hold, its residual inventory of components. A contract that reads state, calls into an address a user can choose, and then settles on what it read before gives that user a window to rewrite the state, and a pre-issue hook is one place such a call can sit. crypto.training tags the case as an incorrect state transition, missing validation, an unsafe external call and cross-contract reentrancy all at once.
Olympix reads a contract the way an attacker does, as states, transitions and entry points, and it takes in the surrounding design, here how ExchangeIssuance, BasicIssuanceModule and a SetToken's manager hook fit together, to infer the invariants a system relies on but never writes down. For this flow the invariant is one sentence: the component units ExchangeIssuance settles with must equal the component units it read before calling out.
The engine maps every path through issueSetForExactToken and uses signals to pick which ones to attack. Olympix's static analysis surfaces the signal in this case: an external call into a contract the caller supplies, sitting between a read of SetToken state and a settlement that depends on that state, with no lock and no re-read around it. Olympix has flagged this read, call out, settle shape before in other codebases, where it's one of the patterns that comes up most often.
That shape is BugPoCer's home ground. Running Counterexample-Guided Invariant Refutation, it holds the read-equals-settle invariant fixed and treats the manager hook as code an attacker gets to write, then executes paths through it until settlement disagrees with the read. A hook that moves the positionMultiplier is enough. The counterexample it returns is this exploit: a user-deployed SetToken whose hook inflates its own multiplier, followed by a settlement that pulls the inflated component amounts from ExchangeIssuance, delivered with the minimal conditions that trigger it and a generated Forge test that replays it.
Because the analysis runs in development, at every merge and before release, it also covers the case audits find hardest to scope. ExchangeIssuance and BasicIssuanceModule each work as designed on their own, and the loss came from how they meet when the SetToken in between belongs to an attacker.
Set Protocol's ExchangeIssuance lost a small amount, and the lesson scales to any contract that calls out mid-flow: once control leaves your code, the numbers you read before are a claim the other side can change. To see how Olympix traces the paths where your contracts call out before they settle and returns a runnable counterexample for any that break, book a technical walkthrough.
Protocol: Set Protocol (Index Coop ExchangeIssuance)
Chain: Ethereum
Date: July 30, 2026, 08:57:23 UTC (first exploit block)
Loss: about $9,600, per SlowMist, including about 436.70 LINK
ExchangeIssuance (victim): 0xc8C8...84fD
Attacker EOA: 0x0736...c99d
Attack contract: 0x388a...4802
Batch Hooked Set (BHSET) SetToken: 0xF7C2...6948
Exploit tx (first of five, LINK drain): 0x7f45...658b
Block: 25644621
SlowMist: Set Protocol exploit alert
Foresight News via Bitget: SlowMist: Set Protocol attacked, with losses of approximately $9,600
crypto.training: Index Coop ExchangeIssuance TOCTOU reproduction