# addGlobalToken() localAdress could be overwritten

Concurrent addGlobalToken calls across async LayerZero hops let a second local token overwrite the first in RootPort, invalidating earlier bridged tokens and losing user deposits.

- Payout: Contest reward
- Severity: medium
- Program: Maia DAO
- Credited: bin2chen
- Disclosed: 2023-11-29
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2023-09-maia-findings/issues/610
- Library page: https://bugbountylibrary.com/r/c4%3A2023-09-maia%3AM-04
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:medium tech:evm tech:solidity technique:race-window technique:source-review vuln:accounting-error vuln:race-condition

**Target:** CoreRootRouter/CoreBranchRouter cross-chain addGlobalToken flow
**Root cause:** _setLocalToken validates the freshly generated _localAddress via isLocalToken(), which is always false for a new address, instead of checking whether the global token is already registered for the destination chain. The earlier check in _addGlobalToken is invalidated by the asynchronous root->branch->root LayerZero round trip.
**Steps:**
1. Call addGlobalToken(ftm) twice concurrently (two pending LayerZero requests) for the same global token.
2. Each branch-side _receiveAddGlobalToken deploys a distinct new local token (0xFA111111, 0xFA222222).
3. First _setLocalToken passes isLocalToken check (new address) and sets mapping to 0xFA111111.
4. Second _setLocalToken also passes since 0xFA222222 is new, overwriting mapping to 0xFA222222.
5. Repro test testAddGlobalTokenOverride in CoreRootBridgeAgentTest.t.sol logs first then override address.
**How found:** Traced the root->branch->root async call sequence and noticed the step 1.2 duplicate check happens long before step 3.2 state write, so concurrency defeats it; then observed step 3.1 checks a regenerated address that can never already exist.
**Exploitation:** Attacker/user bridges out against the first local token during the window between the two _setLocalToken executions; after the second overwrites the mapping, the first local token is invalid and deposited assets are lost.
**Insight:** In async cross-chain flows, duplicate-registration guards placed on the initiating hop are useless; the guard must be re-evaluated at the state-mutating hop against the stable identifier (global token + chain), not a freshly generated address.
