SynapLogicErc20 Router Flash-Loan Over-Mint Exploit
Exploit Transactions
0xc54c00046364b6e889db18c73beee9b81df6b5ca822b6d262b3d30cdf376c4b1Victim Addresses
0x39f36e2e58f36f7e5c17784847fd07da1fee1a32BaseLoss Breakdown
Similar Incidents
MPRO Staking Proxy unwrapWETH Flash-Loan Exploit (Base)
41%Base DUCKVADER infinite mint + Uniswap drain
34%Odos router signature-validation bug enables arbitrary token drains
34%TSURUWrapper onERC1155Received bug mints unbacked tokens and drains WETH
32%Treasury allowance-router abuse drains USDC/USDT/WBTC cross-chain
32%USDC drain via unchecked Uniswap V3-style callback
31%Root Cause Analysis
SynapLogicErc20 Router Flash-Loan Over-Mint Exploit
1. Incident Overview TL;DR
On Base (chainid 8453) at block 41038634 (0x272332a), an unprivileged EOA 0x3aa8bb3a19eecd229cb33fbc03ff549473e30f38 deployed a helper contract 0x3821f686384c231e2F71ea093Fb6189dE803f482 and a staging contract 0x03e0a788e47531aa86b0fd2c44219dc465737c9d via a single contract-creation transaction 0xc54c00046364b6e889db18c73beee9b81df6b5ca822b6d262b3d30cdf376c4b1. In that same transaction, the helper executed a WETH flash loan against pool 0xd0b53d9277642d899df5c87a3966a349a798f224, over-minted 442,345,096 SYP via SynapLogicErc20::mint(3, 0x3821..., _am, 1, false) through sale router 0x39f36e2e58f36f7e5c17784847fd07da1fee1a32, swapped SYP back into WETH/ETH, repaid the loan, and withdrew the remaining ETH to the attacker EOA.
The root cause is a mis-specified sale router design where router 0x39f3…, acting as a privileged relayer222O on SynapLogicErc20 (token 0x2bdd3602fc526aa5cc677cd708375dd2f7c4256f), mints SYP based on its own ETH balance (including flash-loaned funds and prior buyers’ deposits) instead of strictly bounding each mint by the current buyer’s ETH contribution and fixed sale parameters. The attacker’s helper contract computes a flash-loan amount from the router’s ETH balance, routes that ETH back through a buyer-facing sale path, and causes multiple mint(3, 0x3821..., _am, 1, false) calls that allocate 442,345,096 SYP to the helper. Those tokens are then sold into the SYP/WETH pool, draining the router’s entire 27.6465685 ETH balance and yielding a net profit of 27.639647324195906079 ETH for the attacker after gas and L1 fees.
2. Key Background
The relevant system components on Base at pre-state σ_B (immediately before block 41038634) are:
SynapLogicErc20token (0x2bdd3602fc526aa5cc677cd708375dd2f7c4256f), an ERC-20-like token with custom accounting for vesting and exchange balances and role-based minting viarelayer111Oandrelayer222O.- Sale router proxy
0x39f36e2e58f36f7e5c17784847fd07da1fee1a32and its implementation0xc859ac8429fb4a5e24f24a7bed3fe3a8db4fb371, which manage sale parameters (rate, minimum contribution, referral percentages, oracle data, and pair address) and expose buyer-facing functions that ultimately callSynapLogicErc20::mint. - SYP/WETH pool
0xd0b53d9277642d899df5c87a3966a349a798f224, which lends WETH to the helper via a flash-loan-like interface and later receives SYP for swap/repayment. - Canonical WETH9
0x4200000000000000000000000000000000000006, used as the flash-loan asset and the bridge between ERC-20 balances and native ETH.
The SynapLogicErc20 contract’s minting logic shows that special “relayer” roles are allowed to mint and burn SYP on behalf of users:
contract SynapLogicErc20 is Context, IERC20 , Ownable {
using SafeMath for uint256;
mapping(address => uint256) private _vesting;
mapping(address => uint256) private _exchange;
mapping(address => mapping(address => uint256)) private _allowances;
mapping(address => bool) private relayer111O;
mapping(address => bool) private relayer222O;
// ...
function mint(
uint256 _ac, //action
address _u, //user
uint256 _am, //amount
uint256 _set_mode,
bool _status
) external {
if (_ac == 1) {
require(relayer111O[msg.sender], "relayer 1");
relayer111O[_u] = _status;
}
if (_ac == 2) {
require(relayer111O[msg.sender], "relayer 1");
relayer222O[_u] = _status;
}
// add token
if (_ac == 3) {
if (_set_mode == 1) {
require(relayer222O[msg.sender], "relayer 2");
_vesting[_u] = _vesting[_u].add(_am);
emit Transfer(msg.sender, _u, _am);
}
// ...
}
// ...
}
}
This design allows any address in relayer222O to mint arbitrary _am units of SYP into the vesting bucket of any _u, subject only to off-chain discipline or on-chain checks enforced by the caller. The router implementation at 0xc859… is such a relayer222O and is meant to enforce sale-specific rules, but as the exploit shows, it can be tricked into over-minting when its ETH balance is manipulated via flash loans.
The attacker’s helper contract at 0x3821… is decompiled in Heimdall artifacts and contains a drainAll() function that explicitly reads the router’s ETH balance and uses it to size a flash loan from the SYP/WETH pool:
contract DecompiledContract {
uint256 store_b;
address public owner;
bytes32 store_c;
/// @custom:selector 0xb29ac03a
/// @custom:signature drainAll() public payable returns (uint256)
function drainAll() public payable returns (uint256) {
// ...
store_b = address(0x39f36e2e58f36f7e5c17784847fd07da1fee1a32).balance >> 0x01;
// loan ≈ router.balance/2 * (1 + 0x1f4 / 0x0f4240) + 1
// ...
(bool success, bytes memory ret0) =
address(0xd0b53d9277642d899df5c87a3966a349a798f224)
.Unresolved_490e6cbc(address(this)); // flash loan from pool
// ...
}
}
This confirms that the helper is designed to consume whatever ETH the router currently holds (including prior buyers’ deposits) and to use that state as the basis for a flash loan and subsequent sale interaction.
3. Vulnerability Analysis & Root Cause Summary
The core vulnerability is a sale-level accounting bug in the SynapLogicErc20 router system: buyer-facing flows ultimately controlled by router 0x39f3… mint SYP based on the router’s aggregate ETH balance and sale parameters, rather than strictly on each buyer’s individual ETH input in the current call. Because router 0x39f3… is a privileged relayer222O on SynapLogicErc20, any mis-specification in its logic translates directly into over-minting of SYP.
In the vulnerable flow exploited here, the router’s mint amount _am used in mint(3, buyer, _am, 1, false) is derived from address(0x39f3…).balance, which includes (a) ETH previously paid by earlier buyers who have not yet been fully “settled” and (b) transient ETH injected via a WETH flash loan from pool 0xd0b5…. The attacker’s helper drainAll() sizes the flash loan proportional to the router’s existing ETH balance, routes that ETH through the sale path, and causes the router to mint 442,345,096 SYP to the helper address 0x3821….
The precise invariant articulated in the analyzer output and confirmed by code and traces is:
- For any publicly accessible sale path that ends in
SynapLogicErc20::mint(3, buyer, amount, 1, false)via router0x39f3…, the minted SYP amountamountmust be a deterministic function of that buyer’s own ETH contribution (msg.value) and static sale parameters (rateOneBnbToToken,percentRefBnb,minBnb, etc.), and must not depend on router0x39f3…’s pre-existing ETH balance, flash-loaned funds, or ETH previously paid by other buyers.
This invariant is concretely broken in the seed transaction 0xc54c…, where the router’s mint amount _am is computed from its ETH balance (inflated via flash loan and existing deposits) and used repeatedly in mint(3, 0x3821…, _am, 1, false) calls. The result is a deterministic but incorrect over-mint of SYP, which is immediately converted to WETH/ETH to drain the router’s treasury.
4. Detailed Root Cause Analysis
This section walks through the exploit mechanism step by step, grounding each step in on-chain traces and contract code.
4.1 Pre-state σ_B and roles
From balance_diff.json and the seed metadata:
- Router
0x39f3…holds 27.6465685 ETH before the seed transaction. - SYP/WETH pool
0xd0b5…holds substantial WETH (4,875,054,800,055,517,286,711 units) and acts as the source and sink for the flash loan. - Attacker EOA
0x3aa8…holds 0.007609 ETH. - SynapLogicErc20
0x2bdd…is deployed with total supply 300,000,000e18, but supply is effectively controlled via vesting/exchange buckets and relayer roles.
The SynapLogicErc20 mint function exposes the following key behavior:
mint(1, u, am, …)andmint(2, u, am, …)manage relayer rolesrelayer111Oandrelayer222O.mint(3, u, am, 1, false)withmsg.senderinrelayer222Oadds_amto_vesting[u]and emits aTransfer(msg.sender, u, _am)event, effectively minting_amSYP tou’s vesting bucket.
The data collector’s mint3_debugtrace_lifetime_search.json confirms:
- Router
0x39f3…has a single priormint(3, u, am, 1, false)call before the exploit, used by owner0xaf4d…to allocate SYP to themselves under normal sale conditions. - Router
0x39f3…has been grantedrelayer222Ostatus via an owner-onlymint(2, _u=0x39f3…)call, so it is authorized to issuemint(3, …)calls.
4.2 Helper drainAll() and flash-loan setup
The helper’s drainAll() implementation (Heimdall decompile) shows how it sizes and requests the flash loan:
function drainAll() public payable returns (uint256) {
// ...
store_b = address(0x39f36e2e58f36f7e5c17784847fd07da1fee1a32).balance >> 0x01;
// enforce arithmetic safety and pool liquidity conditions
// ...
// compute loan amount ~ router.balance/2 + router.balance/2 * 0x1f4 / 0x0f4240 + 1
// ...
(bool success, bytes memory ret0) =
address(0xd0b53d9277642d899df5c87a3966a349a798f224)
.Unresolved_490e6cbc(address(this)); // flash loan
// ...
}
The seed transaction trace (trace.cast.log) shows the helper calling pool 0xd0b5… with a selector 0x490e6cbc and arguments that match a typical Uniswap V2-style flash- or swap-with-callback pattern, where:
var_b = address(this)is the helper.var_cis the flash-loan amount derived from the router’s ETH balance.
The ERC-20 balance diffs confirm that pool 0xd0b5… receives and later returns exactly 6,915,097,946,062,501 WETH units (delta on WETH9 holder 0xd0b5… is +6,915,097,946,062,501).
4.3 Router sale path and over-mint
Once the helper has secured WETH from the pool, it unwraps WETH to ETH (via WETH9 withdraw) and routes that ETH through router 0x39f3… using a sale-specific method with selector 0x670a3267. The Heimdall decompilation of router implementation 0xc859… includes:
/// @custom:selector 0x670a3267
/// @custom:signature Unresolved_670a3267(uint256 arg0, address arg1) public pure
function Unresolved_670a3267(uint256 arg0, address arg1) public pure {
// arg0, arg1 validated but not bounded by msg.value
// internal arithmetic prepares a dynamic array-like structure
// later used to drive sale/mint logic
}
While the decompiled Solidity is partially abstracted, the analyzer’s iteration-3 and iteration-4 artifacts (implementation_eth_calls.json and mint3_debugtrace_lifetime_search.json) demonstrate that:
- Buyer-facing sale functions eventually call SynapLogicErc20
mint(3, u, am, 1, false)via router0x39f3…. - The mint amount
amis computed from router state that depends onaddress(0x39f3…).balanceand configured sale parameters likerateOneBnbToToken, rather than being recomputed solely from the current caller’smsg.value.
During the exploit transaction, the seed trace shows repeated calls:
SynapLogicErc20::mint(3, 0x3821f686384c231e2F71ea093Fb6189dE803f482, 11058627400000000000000, 1, false)invoked multiple times from caller0x39f3….- One larger
mint(3, 0x3821…, 110586274000000000000000, 1, false)call.
The ERC-20 balance diffs aggregate these into a net SYP increase:
{
"token": "0x2bdd3602fc526aa5cc677cd708375dd2f7c4256f",
"holder": "0x3821f686384c231e2f71ea093fb6189de803f482",
"before": "0",
"after": "442345096000000000000000",
"delta": "442345096000000000000000",
"contract_name": "SynapLogicErc20"
}
Because the helper’s flash-loan amount and the router’s mint amount are both derived from the router’s ETH balance (which includes other users’ funds), these mints violate the invariant that each buyer’s minted SYP must correspond only to their own ETH contribution and fixed sale parameters. Instead, the attacker appropriates claims on prior buyers’ deposits and the temporary flash-loan balance.
4.4 Swaps, loan repayment, and ETH extraction
After the over-mint, the helper contract swaps SYP back into WETH/ETH using pool 0xd0b5…, repays the flash loan plus fee, and withdraws the remaining ETH:
trace.cast.logshows SYP transfers from0x3821…to0xd0b5…and corresponding WETH/ETH flows back to the helper.- The same trace includes WETH9
withdrawanddepositcalls, with WETH’s native balance delta:
{
"address": "0x4200000000000000000000000000000000000006",
"before_wei": "211523522328441029910290",
"after_wei": "211523529243538975972791",
"delta_wei": "6915097946062501"
}
Once the flash loan is repaid, the helper calls its withdraw(address) function to send the remaining ETH to the attacker EOA:
/// @custom:selector 0x51cff8d9
/// @custom:signature withdraw(address arg0) public payable
function withdraw(address arg0) public payable {
// ...
(bool success, bytes memory ret0) = address(arg0).transfer(address(this).balance);
// ...
}
The native balance diffs confirm that:
- Router
0x39f3…loses its entire ETH balance:-27.6465685 ETH. - Attacker EOA
0x3aa8…gains+27.63965036312492 ETH(before gas).
{
"address": "0x39f36e2e58f36f7e5c17784847fd07da1fee1a32",
"before_wei": "27646568500000000000",
"after_wei": "0",
"delta_wei": "-27646568500000000000"
},
{
"address": "0x3aa8bb3a19eecd229cb33fbc03ff549473e30f38",
"before_wei": "7609000000000000",
"after_wei": "27647259363124921789",
"delta_wei": "27639650363124921789"
}
Gas and L1 data fees total 3,038,925,015,710 wei, giving a net ETH profit:
- Gross ETH gain: 27.63965036312492 ETH.
- Fees: 0.00000303892501571 ETH.
- Net profit: 27.639647324195906079 ETH.
4.5 Invariant and breakpoint recap
The invariant and breakpoint are thus concretely instantiated as:
- Invariant: Buyer-facing sale paths that culminate in
mint(3, buyer, amount, 1, false)through router0x39f3…must mint SYP strictly as a deterministic function of the caller’s ETH input and fixed sale parameters, independent of router ETH balance, flash-loaned funds, or other users’ contributions. - Breakpoint: In tx
0xc54c…, within the flash-loan callback ofhelper.drainAll(), router0x39f3…callsmint(3, 0x3821…, _am, 1, false)multiple times where_amis derived fromaddress(0x39f3…).balance, violating the invariant and enabling over-mint of 442,345,096 SYP to0x3821….
5. Adversary Flow Analysis
The entire exploit is realized in a single adversary-crafted transaction on Base:
- Transaction:
0xc54c00046364b6e889db18c73beee9b81df6b5ca822b6d262b3d30cdf376c4b1- Chain: Base (8453)
- Sender: EOA
0x3aa8bb3a19eecd229cb33fbc03ff549473e30f38 - Type: EIP-1559 (type-2) contract creation with
to = nullandvalue = 0
The adversary’s execution flow is:
- Contract deployment: The EOA deploys helper
0x3821…and staging contract0x03e0…in the creation phase of tx0xc54c…. Seed metadata and receipt show creation addresses and link them to the attacker EOA. - Flash loan initiation: The helper’s
drainAll()is invoked, reading router0x39f3…’s ETH balance and computing a loan size derived from approximately half that balance scaled by configured fee parameters. It then calls pool0xd0b5…via selector0x490e6cbcto borrow WETH, with the pool sending WETH back to the helper and expecting repayment in the callback. - WETH unwrap to ETH: The helper calls WETH9
withdrawon0x4200…06, converting borrowed WETH into native ETH held by the helper. - Sale router interaction: The helper forwards ETH through router
0x39f3…, invoking a sale function rooted in selector0x670a3267and ultimately causing router0x39f3…(asrelayer222O) to callSynapLogicErc20::mint(3, 0x3821…, _am, 1, false)multiple times. The mint amount_amis computed usingaddress(0x39f3…).balanceplus sale parameters, not the helper’s actualmsg.value. - SYP liquidation: After accumulating 442,345,096 SYP in
0x3821…, the helper swaps SYP back into WETH/ETH by interacting with pool0xd0b5…. Trace logs show SYP transfers from0x3821…to0xd0b5…and corresponding WETH/ETH flows back to the helper. - Loan repayment: From the returned WETH/ETH, the helper repays the full flash loan plus fee to pool
0xd0b5…, leaving a surplus of ETH held by the helper. - Profit withdrawal: The helper calls
withdraw(0x3aa8…), transferring the remaining ETH to the attacker EOA. Balance diffs confirm the final attacker ETH balance increase and router ETH depletion.
Throughout this flow, the adversary uses only publicly callable functions:
helper.drainAll()is a permissionless method on an attacker-owned contract.pool.Unresolved_490e6cbc, router0x39f3…’s buyer-facing path (rooted in0x670a3267), and WETH9 functions are all callable without special roles.- Privilege escalation occurs only indirectly, by leveraging router
0x39f3…’s pre-existingrelayer222Ostatus on SynapLogicErc20 and its flawed accounting logic.
6. Impact & Losses
The primary financial impact is the loss of ETH from the Synap sale router’s control and the over-minting of SYP:
-
ETH loss (router treasury):
- Router
0x39f3…native balance delta:-27.6465685 ETH. - Attacker EOA
0x3aa8…native balance delta:+27.63965036312492 ETH. - Gas + L1 data fees:
0.00000303892501571 ETH. - Net attacker profit:
27.639647324195906079 ETHin the reference asset ETH.
- Router
-
SYP over-minting:
- Helper
0x3821…SYP delta:+442,345,096 SYP(18 decimals). - These tokens are sold immediately into the SYP/WETH pool
0xd0b5…, indirectly converting the mis-minted supply into ETH at the expense of the router’s treasury and SYP’s effective backing.
- Helper
The attack is a textbook ACT opportunity:
- It uses only canonical on-chain data (router ETH balance, pool reserves, token balances).
- It depends solely on publicly observable contracts and functions (no private keys or privileged roles held by the attacker).
- The profit predicate is deterministic and reproducible for any adversary able to deploy a helper similar to
0x3821…and submit a transaction equivalent to0xc54c…at the same pre-state σ_B.
7. References
-
Seed transaction metadata and receipt (Base tx 0xc54c…):
artifacts/root_cause/seed/8453/0xc54c00046364b6e889db18c73beee9b81df6b5ca822b6d262b3d30cdf376c4b1/metadata.jsonartifacts/root_cause/data_collector/iter_1/tx/8453/0xc54c00046364b6e889db18c73beee9b81df6b5ca822b6d262b3d30cdf376c4b1/receipt.json
-
Balance and ERC-20 diffs for tx 0xc54c…:
artifacts/root_cause/seed/8453/0xc54c00046364b6e889db18c73beee9b81df6b5ca822b6d262b3d30cdf376c4b1/balance_diff.json
-
Victim token and core contracts:
- SynapLogicErc20 verified source (
0x2bdd…):artifacts/root_cause/seed/8453/0x2bdd3602fc526aa5cc677cd708375dd2f7c4256f/src/Contract.sol - WETH9 verified source (
0x4200…06):artifacts/root_cause/seed/8453/0x4200000000000000000000000000000000000006/src/Contract.sol
- SynapLogicErc20 verified source (
-
Router, helper, staging, and pool code/traces:
- Router implementation
0xc859…decompiled Solidity and bytecode:artifacts/root_cause/data_collector/iter_3/contract/8453/0xc859ac8429fb4a5e24f24a7bed3fe3a8db4fb371/decompile - Helper
0x3821…Heimdall decompilation (drainAll and withdraw):artifacts/root_cause/data_collector/iter_3/contract/8453/0x3821f686384c231e2F71ea093Fb6189dE803f482/decompile - Staging contract
0x03e0…artifacts:artifacts/root_cause/data_collector/iter_3/contract/8453/0x03e0a788e47531aa86b0fd2c44219dc465737c9d - SYP/WETH pool
0xd0b5…decompiled Solidity and bytecode:artifacts/root_cause/data_collector/iter_1/contract/8453/0xd0b53d9277642d899df5c87a3966a349a798f224/decompile
- Router implementation
-
On-chain trace evidence:
- Seed transaction full trace (
cast run -vvvvvstyle):artifacts/root_cause/seed/8453/0xc54c00046364b6e889db18c73beee9b81df6b5ca822b6d262b3d30cdf376c4b1/trace.cast.log - Router lifetime search for
mint(3,…)calls:artifacts/root_cause/data_collector/iter_4/contract/8453/0x39f36e2e58f36f7e5c17784847fd07da1fee1a32/mint3_debugtrace_lifetime_search.json
- Seed transaction full trace (
-
Data collection summary:
artifacts/root_cause/data_collector/data_collection_summary.json