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.

0
Critical Issues
0
High Risk Issues
0
Medium Risk Issues
1
Low Risk (UX)

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

Reentrancy Attacks
OpenZeppelin's ReentrancyGuard on flashLoan() prevents recursive calls. Tested scenarios: recursive flash loans, cross-contract reentrancy, provider manipulation during loan. All blocked.
Flash Loan Attacks
Repayment verified before distribution. Failed loans revert entirely. Atomic execution ensures all-or-nothing.
Price Oracle Manipulation
No price oracles used. Not vulnerable to oracle attacks. No external dependencies.
Front-Running
Flash loans are atomic. No state changes between transactions. MEV-resistant design.
Gas Griefing
Borrowers pay for their own gas. No gas forwarding to callbacks. If borrower runs out of gas, transaction reverts safely.
ERC777 Hooks
Even if ERC777 tokens call back via hooks, nonReentrant blocks reentrant calls. SafeERC20 used for all transfers.
Continuous Borrowing
Providers can pause anytime to stop new loans. Earn fees while active. Max borrow limit (50% default) prevents pool drainage.
Integer Overflow/Underflow
Solidity 0.8.24 built-in overflow protection. OpenZeppelin Math.mulDiv for critical calculations. Unlimited commitments handled safely.

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() or emergencyWithdraw() 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 → ContractWrite Contract
  • Connect deployer wallet
  • Call proposeTokenConfig or proposeProfitWithdrawal
  • Copy the emitted ChangeProposed hash (optional)

2. Admin (Vultisig vault) executes

  • Open the same contract on Etherscan from the Vultisig wallet
  • Call executeTokenConfig or executeProfitWithdrawal with identical parameters
  • Vultisig automatically co-signs the transaction across devices
  • Transaction emits ChangeExecuted for 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

Both Loans in Same Block!

Two concurrent flash loans executed successfully in block 9704215 on Sepolia testnet, proving the system handles simultaneous borrowing without interference.

Small Loan (0.001 WETH)
Gas Used:107,339
Fee:0.0000002 WETH
Providers:Single provider
Status:✅ Success
Large Loan (0.02 WETH)
Gas Used:161,512
Fee:0.000004 WETH
Providers:Multiple providers
Status:✅ Success
Key Findings:
  • ✅ 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

Zero Amount Loan
Reverts with InvalidAmount()
Exceeds Max Borrow
Reverts with ExceedsMaxBorrowLimit()
Insufficient Liquidity
Reverts with InsufficientCommittedLiquidity()
Expired Commitment
Auto-paused, skipped in calculations
Paused Provider
Skipped in liquidity calculations
Overflow in totalCommitted
Capped at MaxUint256
Failed Loan Repayment
Entire transaction reverts
Callback Failure
Loan reverts, no distribution

Critical Code Paths

Per-Token Config & Limits
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
}
Flash Loan Guard
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

Contract Version
FlashBankRouter v2.0 (WETH-based with owner profits)
Solidity Version
0.8.24 (paris EVM)
Dependencies
• OpenZeppelin Contracts (Ownable, ReentrancyGuard, SafeERC20)
• OpenZeppelin Math (mulDiv for overflow prevention)
• ERC-20 Permit (EIP-2612)
Audit Date
2025-11-25
Audit Status
Self-audited (external review pending)62+ automated tests
Current Sepolia Deployment
Router: 0x9a4FbC70b30f32006A3fe834173D16b7A0e4E7D4
Admin: 0x3CD6BbF16599Af7FDe6F4b7C8b6FD6Bea4EDc191
Owner: 0x4F0B3C7fdf5D7C3C7179E1E180b28D23a16fd036
Demo Borrower: 0xFD0a29b84533d9CEF69e63311bb766236f09a454

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

Self-Audited · 62+ Automated Tests · Source Verified

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.