Security Audit
Comprehensive security analysis and testing results
Live on Mainnet
All critical security tests passed. Self-audited with 62+ automated tests. Source code verified on Etherscan. External audit pending.
Key Security Features
Funds Stay in Your Wallet
Your WETH never leaves your wallet. The router only pulls funds during active flash loans and returns them immediately with your fee share.
Full Provider Control
Pause your commitment at ANY time. Set expiry dates and adjust limits. Your funds, your rules.
Owner Cannot Steal Funds
No withdrawal functions exist for provider funds. Owner can only withdraw legitimately earned fees (capped at 1% of loans).
Fee Limits Enforced
Fees hardcoded between 0.01% - 1%. Owner fee capped at 100% of the fee. No excessive fees possible.
Ownership & Rescue Hardening
transferOwnership and renounceOwnership are disabled. Ownership transfers and ERC-20/ETH rescue flows now follow the same dual-signature path as fee changes.
Flexible Token Wrapper
The wrapper address is only used when loans must be paid in native ETH (e.g. WETH unwrap for toNative demos). Future tokens (LINK, wstETH, etc.) can set wrapper = 0 to be loaned as themselves.
Reentrancy Protection
OpenZeppelin's ReentrancyGuard prevents reentrancy attacks on flash loan execution.
Overflow Protection
Solidity 0.8.24 built-in overflow/underflow checks plus OpenZeppelin Math.mulDiv for safe percentage calculations on large values.
Attack Resistance
ReentrancyGuard on flashLoan() prevents recursive calls. Tested scenarios: recursive flash loans, cross-contract reentrancy, provider manipulation during loan. All blocked.nonReentrant blocks reentrant calls. SafeERC20 used for all transfers.Reentrancy Protection Deep Dive
Question: Can someone flash loan into the pool and immediately flash loan out?
Answer: NO. OpenZeppelin's ReentrancyGuard prevents all recursive flash loan attempts.
How It Works
1. Lock Acquired: When flashLoan() is called, a global lock is set
2. Funds Transferred: Router pulls liquidity and sends to borrower
3. Callback Executed: Borrower's executeFlashLoan() runs
4. Reentrancy Blocked: Any attempt to call flashLoan() again reverts with "ReentrancyGuard: reentrant call"
5. Repayment Verified: Router checks balance increased by amount + fee
6. Lock Released: Only after successful completion
Attack Scenarios Tested & Blocked
Recursive Flash Loan
Attacker tries to call flashLoan() from within their callback
✅ BLOCKED - Reverts immediately
Cross-Contract Reentrancy
Attacker uses helper contract to call flashLoan() during callback
✅ BLOCKED - Lock is global, not per-contract
Provider Manipulation
Attacker tries to modify commitments during active loan
✅ SAFE - Loan uses snapshot, changes don't affect active loan
Read-Only Reentrancy
Attacker calls view functions to get manipulated state
✅ SAFE - No price oracles or external dependencies
Multiple Layers of Defense
1. ReentrancyGuard
Primary protection on flashLoan()
2. Atomic Transactions
All-or-nothing execution
3. Balance Verification
Must receive amount + fee back
4. Non-Custodial
Funds stay in provider wallets
Full Analysis: See REENTRANCY_ANALYSIS.md in our GitHub repository for detailed attack scenarios, test cases, and code examples.
Admin Privilege Analysis
Owner Cannot Steal Funds
Finding: Owner has NO ability to withdraw provider funds.
Evidence:
- No
withdraw()oremergencyWithdraw()functions - Only
withdrawOwnerProfits()exists for legitimate fees - Providers' funds stay in their own wallets
- Router only has approval to pull during active loans
Fee Limits Are Enforced
Hardcoded Limits:
- Minimum Fee: 0.01% (1 bps)
- Maximum Fee: 1% (100 bps)
- Owner Fee: 0-100% of the fee (max 1% of loan)
- Cannot be bypassed or changed beyond these limits
Dual-Signature Control (2-of-2 Multi-Sig)
Security Design: Critical operations require TWO independent signatures.
How It Works:
- Step 1: Owner (deployer) proposes change
- Step 2: Admin (Vultisig vault) approves and executes
- Both must agree for config changes or profit withdrawals
- Single compromised key cannot modify the protocol
Protected Operations:
- Fee configuration changes
- Pool limit adjustments
- Owner profit withdrawals
- Token enable/disable
- Ownership transfer (propose + execute)
- ERC-20 / ETH rescue transactions
Admin Address: 0xC021...319e7 (Vultisig vault)
Testnets can override via TESTNET_ADMIN_ADDRESS. Current Sepolia admin is 0x3CD6...c191 for compatibility with wallets that do not support Sepolia.
How to Approve Changes (via Etherscan)
Vultisig uses TSS, so there is no single private key to paste into a CLI. Everything can be performed directly from the Write Contract tab on Etherscan:
1. Owner (deployer device) proposes
- Open router contract on Etherscan → Contract → Write Contract
- Connect deployer wallet
- Call
proposeTokenConfigorproposeProfitWithdrawal - Copy the emitted
ChangeProposedhash (optional)
2. Admin (Vultisig vault) executes
- Open the same contract on Etherscan from the Vultisig wallet
- Call
executeTokenConfigorexecuteProfitWithdrawalwith identical parameters - Vultisig automatically co-signs the transaction across devices
- Transaction emits
ChangeExecutedfor audit trail
Need a refresher? See the dual-control runbook for screenshots and detailed instructions.
Owner Can Disable Token (Emergency)
Impact: Owner can set enabled = false to prevent new flash loans.
Mitigation:
- Existing commitments remain intact
- Providers can still pause/withdraw
- No funds are locked
- Only prevents NEW flash loans
Verdict: This is a safety feature for emergency shutdown, not a vulnerability.
Concurrent Flash Loan Test Results
Two concurrent flash loans executed successfully in block 9704215 on Sepolia testnet, proving the system handles simultaneous borrowing without interference.
- ✅ Providers' WETH stayed in their wallets
- ✅ Router pulled liquidity on-demand
- ✅ No interference between concurrent loans
- ✅ Owner accumulated 0.000000084 WETH in profits (2% of fees)
- ✅ Borrowers only needed fees, not full loan amounts
What We DON'T Do (Security by Design)
❌ No Custody of Funds
We never hold your WETH. Funds stay in your wallet. We only have approval to pull during active loans (milliseconds), then immediately return them.
❌ No Price Oracles
No external price feeds = no oracle manipulation attacks. We don't need prices because we verify repayment by checking balances directly.
❌ No Upgradeable Proxies
Contract logic is immutable. What you see is what you get. No hidden upgrade mechanisms that could change behaviour later.
❌ No Complex Dependencies
Only battle-tested OpenZeppelin contracts. No experimental libraries. No external protocol dependencies that could fail.
❌ No Governance Tokens (Yet)
No token that could be exploited for governance attacks. Dual-signature control provides security without token complexity.
❌ No Flash Loan Voting
If we add governance, flash loan voting will be blocked. Can't borrow tokens to manipulate votes.
❌ No Automatic Liquidations
We don't manage collateral or debt positions. No liquidation bots. No cascading liquidations. Just pure flash loans.
❌ No Timelock Bypass
Critical changes require TWO signatures (owner + admin). No single key can bypass this. No emergency admin override.
Known Limitations & Mitigations
Balance Tracking (Low Risk)
Issue: If a provider receives or sends WETH externally, the totalCommitted value doesn't automatically update.
Impact:
- ✅ No fund loss possible (router checks actual balance)
- ⚠️ UI might show misleading "Total Committed" value
- ⚠️ Loans might fail if provider sent WETH away
Mitigation:
- Router checks actual balance at pull time
- Never over-pulls from providers
- Website uses
getActualAvailableLiquidity()for accurate display - Commitments shown as intent, not guarantee
No Forced Withdrawal During Loan
Behavior: Providers cannot force-withdraw WETH while it's actively in use for a flash loan.
Rationale:
- Flash loans are atomic (single transaction)
- Funds return immediately after loan completes
- This ensures borrowers can repay
- Providers can pause to prevent NEW loans
Edge Cases Handled
Critical Code Paths
struct TokenConfig {
bool enabled;
bool supportsPermit;
uint16 feeBps; // 0.01% – 1%
uint256 maxFlashLoan; // absolute cap
address wrapper; // WETH or native bridge
uint16 maxBorrowBps; // % of pool borrowable
uint16 ownerFeeBps; // owner's cut of fee
}uint256 maxBorrow = Math.mulDiv( totalCommitted[token], config.maxBorrowBps, FEE_DENOMINATOR ); if (amount > maxBorrow) revert ExceedsMaxBorrowLimit();
Unlimited commitments previously risked overflowing totalCommitted * maxBorrowBps. We now calculate borrow caps with Math.mulDiv, ensuring the percentage guard holds for any pool size.
Testing & Verification
Automated Coverage
- ✓ 62+ Hardhat/Foundry tests
- ✓ Dedicated failure-path tests (FlashLoanFailed, paused router)
- ✓ Demo borrower integration tests
- ✓ Concurrent loan tests (same block)
- ✓ Owner privilege tests
- ✓ Provider control tests
Manual Review Checklist
- ✓ Verified bytecode on Etherscan
- ✓ Checked owner cannot drain WETH
- ✓ Ensured Math.mulDiv guards unlimited commitments
- ✓ Confirmed fee limits enforced
- ✓ Verified reentrancy protection
- ✓ Tested balance tracking
Contract Information
Bug Bounty & Responsible Disclosure
Found something concerning? Please disclose responsibly.
Critical issues are rewarded. Provide reproduction steps, impact analysis, and recommended fixes. We take security seriously and will respond promptly to all reports.
Security Summary
The FlashBankRouter contract has passed comprehensive internal security testing and demonstrates:
- No critical vulnerabilities found in internal review
- Provider fund safety with non-custodial architecture
- Strict admin privilege limitations via dual-control
- Comprehensive attack resistance (reentrancy, overflow, front-running)
- Immutable contract — no upgrade proxies
Contract verified on Etherscan. Source code is open for independent review. External audit pending — use at your own risk.