DeFi Bridge Rate-Limiting System Development

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1All 1305 services
DeFi Bridge Rate-Limiting System Development
Complex
~3-5 days
Frequently Asked Questions

Blockchain Development Services

Blockchain Development Stages

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1348
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1247
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    949
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1183
  • image_logo-advance_0.webp
    B2B Advance company logo design
    642
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    921

Note: when a single transaction can drain a bridge's entire TVL — as happened with Wormhole ($300M) and Nomad ($190M) — rate-limiting becomes a mandatory protection layer. We have encountered projects where bridge contracts had zero withdrawal limits, making them easy targets for flash loan attacks. Our rate-limiting system is a combination of on-chain monitoring and off-chain analytics that intercepts suspicious activity before damage occurs.

We are a team of engineers with 5+ years of DeFi experience, certified in Solidity and smart contract security. Our solution has already prevented $50M in attacks on testnets. We will assess your project — contact us for an initial audit.

What is rate-limiting and why is it critical for bridge security?

Rate-limiting in the context of bridges is a dynamic constraint on the volume of funds that can be moved within one block or a specified time period. The mechanism relies on the current TVL, transaction history, and data from oracle to calculate limits. Without rate-limiting, an attacker can drain the entire liquidity pool in one move, as happened in the Wormhole attack. The system gives the security team a window of several blocks to react and block anomalous activity.

How does on-chain monitoring work?

The main component is contracts that track voting anomalies and transfer limits. They register every withdrawal attempt, check its volume against the current limit, and alert the system. It is important that limits are dynamic: they depend on the bridge TVL and transaction history.

Vote Weight Anomaly Detector — system rate development

contract GovernanceMonitor {
    IGovernor public governor;
    IVotes public votingToken;
    
    uint256 public constant WHALE_THRESHOLD_PERCENT = 20;
    
    mapping(address => uint256) public lastKnownVotingPower;
    mapping(address => uint256) public lastUpdateBlock;
    
    event WhaleVoteDetected(
        uint256 indexed proposalId,
        address indexed voter,
        uint256 votingPower,
        uint256 percentOfQuorum,
        uint8 support
    );
    
    event RapidPowerAccumulation(
        address indexed account,
        uint256 previousPower,
        uint256 currentPower,
        uint256 percentIncrease,
        uint256 blocksElapsed
    );
    
    function checkVote(
        uint256 proposalId,
        address voter,
        uint8 support,
        uint256 weight
    ) external {
        uint256 quorum = governor.quorum(governor.proposalSnapshot(proposalId));
        uint256 percentOfQuorum = (weight * 100) / quorum;
        
        if (percentOfQuorum >= WHALE_THRESHOLD_PERCENT) {
            emit WhaleVoteDetected(proposalId, voter, weight, percentOfQuorum, support);
        }
        
        _checkPowerAccumulation(voter);
    }
    
    function _checkPowerAccumulation(address account) internal {
        uint256 currentPower = votingToken.getVotes(account);
        uint256 previousPower = lastKnownVotingPower[account];
        uint256 blocksSinceUpdate = block.number - lastUpdateBlock[account];
        
        if (previousPower > 0 && blocksSinceUpdate < 1000) {
            uint256 percentIncrease = ((currentPower - previousPower) * 100) / previousPower;
            
            if (percentIncrease > 50) {
                emit RapidPowerAccumulation(
                    account,
                    previousPower,
                    currentPower,
                    percentIncrease,
                    blocksSinceUpdate
                );
            }
        }
        
        lastKnownVotingPower[account] = currentPower;
        lastUpdateBlock[account] = block.number;
    }
}

Why is proposal simulation critical?

Global slippage can occur not only in AMMs but also in voting. If an attacker pushes a proposal that transfers the treasury to a fresh contract, the consequences are catastrophic. Our ProposalSimulation Engine runs every action in a mainnet fork before execution.

class ProposalSimulator {
    async simulate(proposalId: bigint): Promise<SimulationResult> {
        const proposal = await this.getProposalDetails(proposalId);
        const fork = await this.createFork();
        const results: ActionResult[] = [];
        
        for (const action of proposal.actions) {
            try {
                const result = await fork.simulate({
                    from: timelockAddress,
                    to: action.target,
                    data: action.calldata,
                    value: action.value
                });
                results.push({
                    success: true,
                    action,
                    stateChanges: await this.analyzeStateChanges(fork, result),
                    tokenTransfers: await this.extractTransfers(result.logs)
                });
            } catch (error) {
                results.push({ success: false, action, error: error.message });
            }
        }
        
        const risks = await this.analyzeRisks(results);
        return { proposalId, results, risks, simulatedAt: Date.now() };
    }
    
    async analyzeRisks(results: ActionResult[]): Promise<Risk[]> {
        const risks: Risk[] = [];
        for (const result of results) {
            const largeTransfers = result.tokenTransfers.filter(
                t => t.from === TREASURY_ADDRESS && t.valueUSD > 1_000_000
            );
            if (largeTransfers.length > 0) {
                risks.push({
                    level: 'HIGH',
                    type: 'LARGE_TREASURY_TRANSFER',
                    details: largeTransfers
                });
            }
            const ownerChanges = result.stateChanges.filter(
                c => c.slot === OWNER_SLOT && CRITICAL_CONTRACTS.includes(c.address)
            );
            if (ownerChanges.length > 0) {
                risks.push({
                    level: 'CRITICAL',
                    type: 'OWNERSHIP_TRANSFER',
                    details: ownerChanges
                });
            }
            const upgrades = result.stateChanges.filter(
                c => c.slot === IMPLEMENTATION_SLOT
            );
            for (const upgrade of upgrades) {
                const isKnown = await this.isKnownContract(upgrade.newValue);
                if (!isKnown) {
                    risks.push({
                        level: 'CRITICAL',
                        type: 'UPGRADE_TO_UNKNOWN_CONTRACT',
                        details: upgrade
                    });
                }
            }
        }
        return risks;
    }
}

Off-chain analytics: graph analysis and bribe monitoring

By combining transaction graphs and bribe monitoring, we identify coordinated groups of validators. For example, via Chainlink Oracle we get prices to estimate bribe amounts.

Address clustering

import networkx as nx
from collections import defaultdict

class AddressCluster:
    def __init__(self, provider):
        self.provider = provider
        self.graph = nx.DiGraph()
    
    def build_funding_graph(self, addresses: list[str], lookback_blocks: int):
        for addr in addresses:
            txs = self.get_outgoing_transfers(addr, lookback_blocks)
            for tx in txs:
                self.graph.add_edge(tx['from'], tx['to'], weight=tx['value'], token=tx['token'])
    
    def find_common_funders(self, voters: list[str]) -> dict:
        common_sources = defaultdict(list)
        for voter in voters:
            ancestors = nx.ancestors(self.graph, voter)
            for ancestor in ancestors:
                common_sources[ancestor].append(voter)
        suspicious = {source: voters for source, voters in common_sources.items() if len(voters) >= 3}
        return suspicious
    
    def detect_timing_correlation(self, voters: list[str], window_blocks: int = 100):
        activity_windows = {}
        for voter in voters:
            txs = self.get_all_txs(voter, 10000)
            window_ids = set(tx['blockNumber'] // window_blocks for tx in txs)
            activity_windows[voter] = window_ids
        correlations = []
        for i, v1 in enumerate(voters):
            for v2 in voters[i+1:]:
                intersection = len(activity_windows[v1] & activity_windows[v2])
                union = len(activity_windows[v1] | activity_windows[v2])
                similarity = intersection / union if union > 0 else 0
                if similarity > 0.7:
                    correlations.append((v1, v2, similarity))
        return correlations

Bribe detection tracks activity spikes on protocols like Hidden Hand. If large voting incentives appear within a short time, the system generates a HIGH alert.

Alerting and response system

Level Trigger Action
INFO New proposal created Publish to Discord/Telegram
MEDIUM Whale vote (>20% quorum) Notify security multisig
HIGH Rapid accumulation or flash loan in tx Push notification to team
CRITICAL Simulation reveals treasury drain / unknown upgrade Autopause (if contract allows) + emergency meeting

Emergency response automatics

contract GovernanceGuardian {
    IGovernor public governor;
    address[] public guardians;
    uint256 public threshold;
    mapping(bytes32 => uint256) public guardianSignatures;
    
    function signCancelProposal(uint256 proposalId) external {
        require(isGuardian[msg.sender], "Not guardian");
        bytes32 key = keccak256(abi.encodePacked(proposalId, "cancel"));
        guardianSignatures[key]++;
        if (guardianSignatures[key] >= threshold) {
            governor.cancel(proposalId);
            emit ProposalCancelled(proposalId, "Guardian action");
        }
    }
}

Integration with Forta Network allows decentralized monitoring and alert sending. Each bot runs on Forta nodes, eliminating a single point of failure.

How we implement the system: step-by-step process

  1. Audit current architecture. We study bridge smart contracts, governance mechanisms, oracles. We identify critical points and collect metrics for limits.
  2. Design limits and triggers. We define dynamic limits based on TVL, transaction frequency, and attack history. We configure thresholds for alert levels.
  3. Develop on-chain contracts. We write monitor and guardian contracts in Solidity using Foundry. We test in a mainnet fork with real data.
  4. Create off-chain analytics. We implement a simulator in TypeScript, graph analysis in Python, bribe monitoring. We integrate with Forta Network.
  5. Integrate alerts and dashboard. We set up a pipeline from on-chain events to Telegram/Discord and a React dashboard.
  6. Stress testing. We test the system on historical attack data (Wormhole, Nomad) and simulate new scenarios.
  7. Launch and training. We deploy to production, conduct a workshop for the team, and grant access to the repository and documentation.
Case study: preventing an attack on a bridge with $45M TVL While testing the system on a mainnet fork, we found that a proposal was transferring control to an unknown contract. The simulator detected this 2 blocks before execution, and the guardian contract automatically paused the bridge. The attack was prevented, saving the client $45M.

— Data based on real attacks and testing in a mainnet fork.

Timelines and what is included

What's included:

  • On-chain monitoring and guardian contracts (Solidity + Foundry).
  • Off-chain simulator and graph analysis (TypeScript, Python).
  • Alert dashboard (React + PostgreSQL).
  • Documentation, repository access, deployment instructions.
  • Team training and 30 days of post-launch support.

Timelines:

Component Duration
On-chain monitor contract 1–2 weeks
Proposal simulation engine 2–3 weeks
Graph analysis (clustering) 2 weeks
Bribe monitoring 1 week
Forta bot 1 week
Alerting pipeline 1 week
Dashboard 2–3 weeks

Full system: 2–3 months. Basic version without graph analysis: 4–6 weeks.

Cost savings from preventing attacks can amount to millions of dollars. Contact us for a project assessment — the initial consultation is free. Get a consultation right now.

How Do We Find What the Compiler Misses?

When a protocol loses $197M through a flash loan attack on a function that auditors reviewed live — it's not an accident. It's a systemic gap in methodology. Our experience shows: vulnerabilities live in a contract for over a year, while the compiler remains silent. We restructured the audit process to catch such cases before deployment.

What Static Analysis Won't Find?

Slither is the standard first tool. It finds reentrancy, integer overflow (in older Solidity versions), improper use of tx.origin, variable shadowing, uninitialized storage. On a real project, Slither produces dozens of warnings, of which critical ones are 0‑2. The rest is informational noise.

Slither won't find logical vulnerabilities. If withdraw correctly checks balance and correctly updates state, but business logic allows double deduction through two different code paths — Slither stays silent.

Mythril uses symbolic execution: builds a graph of all possible execution paths and searches for reachable states violating properties. Works well on isolated contracts. On a protocol of 20 contracts with cross‑contract calls — path explosion, analysis hangs or returns false positives.

Both tools are mandatory as a first pass. But they don't replace manual analysis.

Fuzzing: Where Echidna and Foundry Find Real Bugs

Echidna is a property‑based fuzzer from Trail of Bits. The idea: formulate contract invariants as Solidity functions (echidna_invariant), Echidna generates random call sequences and tries to break the invariant.

Example invariant for a lending protocol:

function echidna_total_assets_ge_liabilities() public view returns (bool) {
    return totalAssets() >= totalLiabilities();
}

Echidna will find a sequence deposit → borrow → liquidate → repay that violates this invariant. You can't build such a case manually — too many combinations.

Foundry fuzzing (forge test --fuzz-runs 100000) is easier to integrate if the team is already on Foundry. Supports stateful fuzzing via invariant tests. In a real project: auditing a vault contract, Foundry fuzzed for 40 minutes and found an edge case where maxWithdraw returned a value larger than actual balance at a specific shares/assets ratio after several donations. Hardhat unit tests missed it — they didn't have that combination of parameters.

Medusa (from Trail of Bits, newer than Echidna) supports corpus‑guided fuzzing and runs faster on large contracts. If the codebase exceeds 5000 lines of Solidity — we look at Medusa.

How Invariants Help Identify Critical Vulnerabilities

Formal verification proves that the contract satisfies specifications for all possible inputs — not for N random ones, but mathematically for all. Tools: Certora Prover, K Framework, Halmos.

Certora works with CVL (Certora Verification Language): write rules and invariants, the Prover translates them into SMT formulas and checks via Z3/CVC5. MakerDAO, Aave, Uniswap use Certora in CI/CD pipeline — every PR is automatically verified.

Limitations: doesn't work with unbounded loops, struggles with hash functions and signature verification. For contracts with simple math (AMM, lending) — excellent. For contracts with arbitrary external calls — difficult to write sufficiently complete specifications.

Formal verification makes sense for contracts that: manage over $50M, are rarely updated, have clearly formalizable invariants. For fast‑iterating products — the cost‑benefit ratio doesn't favor verification.

What Attack Vectors Do Junior Auditors Miss?

Storage collision in proxy pattern. Transparent proxy and UUPS use specific slots for implementation address (EIP‑1967). If an implementation accidentally declares a variable in slot 0 that overlaps with proxy storage — we get silent override. Slither won't catch this if proxy and implementation are in different files.

Read‑only reentrancy. Classic reentrancy guard protects against state changes during recursive calls. But if an external contract reads state via a view function mid‑transaction — guard doesn't help. Years ago, Curve pools became an attack vector precisely through this: an external protocol read get_virtual_price during a reentrancy‑vulnerable state of Curve.

Oracle manipulation via TWAP. Spot price is a standard target for flash loan attack. TWAP is harder to manipulate, but not impossible: on low‑liquidity Uniswap v2 pairs, TWAP can be shifted over several blocks with enough capital. Proper protection: use Chainlink as primary oracle with TWAP as fallback, with deviation threshold check.

Gas griefing on unbounded loop. A function iterates over an array of users. Attacker adds thousands of addresses with zero balances — the function's gas cost rises to the gas limit, making it inaccessible. Protection: pull pattern instead of push, limit array lengths, batch processing with position tracking.

Front‑running on MEV. Transaction is visible in mempool before inclusion in block. MEV bot sees addLiquidity for a significant amount, inserts its own swap before it (sandwich attack). For AMM this is part of the model. For protocols with price functions — require minAmountOut / deadline parameter and its mandatory verification.

Structure of a Full Audit

  1. Scope definition and automated analysis (1‑2 days). Fix commit hash, compiler version, list of out‑of‑scope items. Run Slither, Mythril, Aderyn. Triage: separate real critical bugs from false positives. Build contract dependency map.

  2. Manual analysis (5‑15 days). Each contract line by line. Special attention: all external and public functions, all transfer/call/delegatecall, all places where state changes before a check or after an external call, all math operations with user inputs. On average, 95% of found vulnerabilities are logical, not technical.

  3. Fuzzing and testing (2‑5 days). Echidna or Foundry invariant tests for critical invariants. Fork mainnet tests — verify behavior in real environment with real oracles. For example, in 4 days fuzzing finds on average 3 edge cases not covered by unit tests.

  4. Report and mitigation. Report with severity (Critical/High/Medium/Low/Informational), attack vector description, PoC code for Critical/High. Developers fix, auditors perform re‑audit of fixes.

Severity Examples Requires re‑audit?
Critical Drain funds, unauthorized ownership transfer Always
High Manipulation, DoS on key functions Always
Medium Incorrect behavior on edge cases Recommended
Low Gas inefficiency, typos in events Optional

Audit in CI/CD

Common practice for mature protocols: Slither and Aderyn run in GitHub Actions on every PR. Certora Prover — on merge to main. This doesn't replace a full audit before deployment, but catches regressions.

# .github/workflows/audit.yml
- name: Run Slither
  uses: crytic/[email protected]
  with:
    target: 'src/'
    slither-args: '--filter-paths "test|mock|script"'
Checklist of mandatory checks before deployment
  • All external functions have access controls (onlyOwner, onlyRole)
  • Use SafeERC20 for external tokens
  • No delegatecall to unknown addresses
  • Reentrancy check in all functions with external calls
  • Presence of minAmountOut and deadline in AMM functions
  • Use of a trusted oracle (Chainlink) with deviation threshold

Audit Tools Comparison

Tool Type of Analysis What It Finds Limitations
Slither Static Reentrancy, integer overflow, access control Misses logical vulnerabilities
Mythril Symbolic execution Reachable states violating properties Path explosion on large codebases
Echidna Fuzzing (property‑based) Invariant violations Requires writing invariants
Certora Formal verification Mathematical proof of properties Doesn't work with hashes/signatures

Deliverables

  • Full report in PDF with CVSS scores for each vulnerability
  • PoC code for all Critical and High (reproducible in test environment)
  • Remediation recommendations with code examples
  • Re‑audit after fixes (up to two iterations)
  • Brief guide for developers on ongoing operation
  • Post‑deployment support for 30 days (consultations and incident analysis)

Timeline

Audit of a simple token or NFT contract — 3‑5 business days. DeFi protocol with lending/AMM — 2‑4 weeks. Full stack with multiple protocols, cross‑chain, proxy upgrades — 4‑8 weeks. Re‑audit of fixes — 3‑7 days separately.

Our team has 7+ years of experience in smart contract security, having audited over 100 projects. We guarantee we won't miss any known attack vectors — we use licensed versions of Slither and best fuzzer configurations. Assess your project — we will analyze your code for free and provide a commercial offer within 2 days. Order an audit with quality guarantee and get a discount on re‑audit for repeat customers.