Calculated from recorded token losses using historical USD prices at the incident time.
0xc54c00046364b6e889db18c73beee9b81df6b5ca822b6d262b3d30cdf376c4b10x39f36e2e58f36f7e5c17784847fd07da1fee1a32BaseOn 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.
The relevant system components on Base at pre-state σ_B (immediately before block 41038634) are:
SynapLogicErc20 token (0x2bdd3602fc526aa5cc677cd708375dd2f7c4256f), an ERC-20-like token with custom accounting for vesting and exchange balances and role-based minting via relayer111O and relayer222O.0x39f36e2e58f36f7e5c17784847fd07da1fee1a32 and its implementation 0xc859ac8429fb4a5e24f24a7bed3fe3a8db4fb371, which manage sale parameters (rate, minimum contribution, referral percentages, oracle data, and pair address) and expose buyer-facing functions that ultimately call SynapLogicErc20::mint.0xd0b53d9277642d899df5c87a3966a349a798f224, which lends WETH to the helper via a flash-loan-like interface and later receives SYP for swap/repayment.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.
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:
SynapLogicErc20::mint(3, buyer, amount, 1, false) via router 0x39f3…, the minted SYP amount amount must 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 router 0x39f3…’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.
This section walks through the exploit mechanism step by step, grounding each step in on-chain traces and contract code.
From balance_diff.json and the seed metadata:
0x39f3… holds 27.6465685 ETH before the seed transaction.0xd0b5… holds substantial WETH (4,875,054,800,055,517,286,711 units) and acts as the source and sink for the flash loan.0x3aa8… holds 0.007609 ETH.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, …) and mint(2, u, am, …) manage relayer roles relayer111O and relayer222O.mint(3, u, am, 1, false) with msg.sender in relayer222O adds _am to _vesting[u] and emits a Transfer(msg.sender, u, _am) event, effectively minting _am SYP to u’s vesting bucket.The data collector’s mint3_debugtrace_lifetime_search.json confirms:
0x39f3… has a single prior mint(3, u, am, 1, false) call before the exploit, used by owner 0xaf4d… to allocate SYP to themselves under normal sale conditions.0x39f3… has been granted relayer222O status via an owner-only mint(2, _u=0x39f3…) call, so it is authorized to issue mint(3, …) calls.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_c is 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).
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:
mint(3, u, am, 1, false) via router 0x39f3….am is computed from router state that depends on address(0x39f3…).balance and configured sale parameters like rateOneBnbToToken, rather than being recomputed solely from the current caller’s msg.value.During the exploit transaction, the seed trace shows repeated calls:
SynapLogicErc20::mint(3, 0x3821f686384c231e2F71ea093Fb6189dE803f482, 11058627400000000000000, 1, false) invoked multiple times from caller 0x39f3….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.
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.log shows SYP transfers from 0x3821… to 0xd0b5… and corresponding WETH/ETH flows back to the helper.withdraw and deposit calls, 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:
0x39f3… loses its entire ETH balance: -27.6465685 ETH.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:
The invariant and breakpoint are thus concretely instantiated as:
mint(3, buyer, amount, 1, false) through router 0x39f3… 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.0xc54c…, within the flash-loan callback of helper.drainAll(), router 0x39f3… calls mint(3, 0x3821…, _am, 1, false) multiple times where _am is derived from address(0x39f3…).balance, violating the invariant and enabling over-mint of 442,345,096 SYP to 0x3821….The entire exploit is realized in a single adversary-crafted transaction on Base:
0xc54c00046364b6e889db18c73beee9b81df6b5ca822b6d262b3d30cdf376c4b1
0x3aa8bb3a19eecd229cb33fbc03ff549473e30f38to = null and value = 0The adversary’s execution flow is:
0x3821… and staging contract 0x03e0… in the creation phase of tx 0xc54c…. Seed metadata and receipt show creation addresses and link them to the attacker EOA.drainAll() is invoked, reading router 0x39f3…’s ETH balance and computing a loan size derived from approximately half that balance scaled by configured fee parameters. It then calls pool 0xd0b5… via selector 0x490e6cbc to borrow WETH, with the pool sending WETH back to the helper and expecting repayment in the callback.withdraw on 0x4200…06, converting borrowed WETH into native ETH held by the helper.0x39f3…, invoking a sale function rooted in selector 0x670a3267 and ultimately causing router 0x39f3… (as relayer222O) to call SynapLogicErc20::mint(3, 0x3821…, _am, 1, false) multiple times. The mint amount _am is computed using address(0x39f3…).balance plus sale parameters, not the helper’s actual msg.value.0x3821…, the helper swaps SYP back into WETH/ETH by interacting with pool 0xd0b5…. Trace logs show SYP transfers from 0x3821… to 0xd0b5… and corresponding WETH/ETH flows back to the helper.0xd0b5…, leaving a surplus of ETH held by the helper.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, router 0x39f3…’s buyer-facing path (rooted in 0x670a3267), and WETH9 functions are all callable without special roles.0x39f3…’s pre-existing relayer222O status on SynapLogicErc20 and its flawed accounting logic.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):
0x39f3… native balance delta: -27.6465685 ETH.0x3aa8… native balance delta: +27.63965036312492 ETH.0.00000303892501571 ETH.27.639647324195906079 ETH in the reference asset ETH.SYP over-minting:
0x3821… SYP delta: +442,345,096 SYP (18 decimals).0xd0b5…, indirectly converting the mis-minted supply into ETH at the expense of the router’s treasury and SYP’s effective backing.The attack is a textbook ACT opportunity:
0x3821… and submit a transaction equivalent to 0xc54c… at the same pre-state σ_B.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.jsonBalance and ERC-20 diffs for tx 0xc54c…:
artifacts/root_cause/seed/8453/0xc54c00046364b6e889db18c73beee9b81df6b5ca822b6d262b3d30cdf376c4b1/balance_diff.jsonVictim token and core contracts:
0x2bdd…): artifacts/root_cause/seed/8453/0x2bdd3602fc526aa5cc677cd708375dd2f7c4256f/src/Contract.sol0x4200…06): artifacts/root_cause/seed/8453/0x4200000000000000000000000000000000000006/src/Contract.solRouter, helper, staging, and pool code/traces:
0xc859… decompiled Solidity and bytecode: artifacts/root_cause/data_collector/iter_3/contract/8453/0xc859ac8429fb4a5e24f24a7bed3fe3a8db4fb371/decompile0x3821… Heimdall decompilation (drainAll and withdraw): artifacts/root_cause/data_collector/iter_3/contract/8453/0x3821f686384c231e2F71ea093Fb6189dE803f482/decompile0x03e0… artifacts: artifacts/root_cause/data_collector/iter_3/contract/8453/0x03e0a788e47531aa86b0fd2c44219dc465737c9d0xd0b5… decompiled Solidity and bytecode: artifacts/root_cause/data_collector/iter_1/contract/8453/0xd0b53d9277642d899df5c87a3966a349a798f224/decompileOn-chain trace evidence:
cast run -vvvvv style): artifacts/root_cause/seed/8453/0xc54c00046364b6e889db18c73beee9b81df6b5ca822b6d262b3d30cdf376c4b1/trace.cast.logmint(3,…) calls: artifacts/root_cause/data_collector/iter_4/contract/8453/0x39f36e2e58f36f7e5c17784847fd07da1fee1a32/mint3_debugtrace_lifetime_search.jsonData collection summary:
artifacts/root_cause/data_collector/data_collection_summary.json