Smart Contract Refactoring

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 1 servicesAll 1306 services
Smart Contract Refactoring
Medium
~2-3 business days
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1051
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    827
  • image_logo-aider_0.jpg
    AIDER company logo development
    762
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    850

Smart Contract Refactoring

A contract written a year ago works, money isn't lost — but each new feature triggers panic: it's unclear what will break. Storage layout ballooned to 30 variables with no grouping logic, functions are 200 lines long, no tests for edge cases. Refactoring a smart contract differs from refactoring regular code in that the cost of a mistake is loss of user funds.

Where Technical Debt Most Often Hides

Unoptimized storage layout. Solidity packs variables into 32-byte slots. If variables are declared as uint128, uint256, uint128 — that's three slots instead of two. On a popular contract with thousands of calls a day, this is real money. We've seen contracts where reordering 8 variables for slot packing reduced gas on write operations by 40%. This isn't optimization for its own sake — these are concrete thousands of dollars in annual user savings.

Unbounded loops as gas griefing vector. Pattern for (uint i = 0; i < users.length; i++) in a contract where users can grow unbounded — this isn't just inefficiency. An attacker adds 10,000 addresses, and the next distribute() call exceeds block limit (30M gas on mainnet). The function becomes unexecutable — contract stuck. Refactoring to a pull pattern with pagination or enumerable mapping solves this structurally.

Reentrancy without guard at cross-function level. ReentrancyGuard from OpenZeppelin protects one function. But if withdraw() is protected and claim() isn't — and both modify the same balance mapping — cross-function reentrancy is possible. This is exactly how the Fei Protocol exploit worked (80M$ in 2022). When refactoring, audit the entire call graph, not just "suspicious" functions.

How We Approach Refactoring

The first step is static analysis via Slither. It finds in 2-3 minutes:

  • reentrancy patterns (including cross-function)
  • uninitialized variables
  • tx.origin authorization
  • incorrect operation order (state change after external call)
  • shadow variables

Slither produces hundreds of warnings on any real contract — the key is separating critical from informational. Then Mythril for symbolic execution on key functions.

Coverage audit. We check what's tested and what isn't. Usually: happy path is covered, edge cases are not. No test for "what if owner calls this function twice in a row". No test for "what happens to the contract after emergency pause". We add tests via Foundry — its fuzzer finds in an hour what manual Hardhat tests didn't find in a month.

Structural refactoring. We extract logic into libraries (Library pattern), separate storage and logic via Diamond pattern (EIP-2535) if the contract is large, apply Check-Effects-Interactions on every function with external calls. We rewrite Events — incorrectly indexed parameters make The Graph queries inefficient.

Gas optimization. Specific patterns:

  • storagememory for read-only operations within a function
  • uint256 instead of uint8 in local variables (EVM operates on 256-bit words, downcasting is more expensive)
  • unchecked { i++ } in loop counters where overflow is impossible (Solidity 0.8+)
  • calldata instead of memory for external function parameters
  • event packing: don't emit unnecessary fields in events
Pattern Gas Savings (approximate)
Variable slot packing 20-40% on SSTORE
memory instead of storage in function 15-30% on reads
unchecked increment 60-80 gas per iteration
calldata instead of memory 50-100 gas per argument
Custom errors vs require strings 50-200 gas per revert

Solidity Version Upgrade

Refactoring often includes migration from 0.6/0.7 to 0.8+. Main changes:

  • Arithmetic overflow/underflow checked by default (SafeMath can be removed)
  • Custom errors via error keyword — cheaper and more informative than revert("string")
  • Immutable variables — save gas on constants set in constructor

Migration from 0.6 to 0.8 isn't just changing pragma. ABI encoding changed, some assembly patterns stopped working, .call.value() replaced with .call{value:}(). We test each change in isolation.

Work Process

Day 1. Static analysis (Slither, Mythril), coverage report, create issue registry with priorities.

Days 2-3. Refactor by priority: critical security issues → gas optimization → readability. Each PR is an isolated change with tests. No "one big commit with 50 changes".

Final. Run Foundry fuzz tests on refactored functions, compare gas reports before/after via forge snapshot.

Timeline — 2-3 days for contracts up to 500 lines. More complex multi-contract systems — up to a week.