# erc777 cross function re-entrancy

Popcorn Vault mints shares before pulling ERC777 tokens, letting a callback re-enter the separate mint() function to double shares per deposit.

- Payout: Contest reward
- Severity: medium
- Program: Popcorn
- Credited: 0xWeiss
- Disclosed: 2023-09-07
- Write-up quality: 6/10
- Original: https://github.com/code-423n4/2023-01-popcorn-findings/issues/453
- Library page: https://bugbountylibrary.com/r/c4%3A2023-01-popcorn%3AM-20
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:medium tech:evm tech:solidity technique:chained technique:source-review vuln:race-condition vuln:reentrancy

**Target:** Vault.sol deposit()/mint() share issuance
**Root cause:** deposit() and mint() call _mint before asset.safeTransferFrom; ERC777 transferFrom invokes a tokensToSend callback on the sender, allowing reentry into the sibling function which lacks a shared nonReentrant guard.
**Steps:**
1. Deploy attacker contract holding ERC777 asset tokens.
2. Call Vault.deposit(assets, receiver); deposit() computes shares and calls _mint(receiver, shares) before pulling tokens.
3. During asset.safeTransferFrom, ERC777 tokensToSend callback fires to attacker contract.
4. In callback, call Vault.mint(shares, receiver) (public, not blocked by deposit's nonReentrant) which mints again before its own transferFrom.
5. Repeat to accumulate ~2x shares per asset deposited.
**How found:** Code reading: noticed _mint precedes safeTransferFrom in both deposit and mint, and that ERC777 transferFrom triggers a sender callback; since deposit is nonReentrant but mint is a separate entrypoint, cross-function reentrancy bypasses the guard.
**Exploitation:** Attacker deposits ERC777 assets; the pre-transfer _mint plus a reentrant call into mint() during the ERC777 callback yields double shares for the same asset amount, diluting the vault.
**Insight:** When a contract has multiple mint/deposit entrypoints sharing state, a nonReentrant modifier on one does not protect the others; ERC777 tokensToSend callbacks fire on the sender during transferFrom, so any state-changing mint before the token pull is reentrant via a sibling function.
