Labs ICT
⭐ Pro Login

Solidity Advanced

Inheritance, modifiers, and gas optimization

2 min read | Blockchain & Web3
⭐

Want the full learning experience?

Get structured courses, certificates, projects, and instructor support with LabsICT Pro.

Explore Pro Courses

Solidity Advanced

Mastering advanced Solidity patterns is essential for writing secure, gas-efficient smart contracts. This lesson covers inheritance, error handling, gas optimization, and common design patterns.

Inheritance and Interfaces


  // Base contract
  contract Ownable {
      address public owner;
      constructor() { owner = msg.sender; }
      modifier onlyOwner() {
          require(msg.sender == owner); _;
      }
  }

  // Inheritance
  contract Token is Ownable {
      string public name;
      function rename(string memory _name) public onlyOwner {
          name = _name;
      }
  }

  // Interface (no implementation)
  interface IERC20 {
      function totalSupply() external view returns (uint);
      function balanceOf(address) external view returns (uint);
  }

  // Abstract contract (partial implementation)
  abstract contract Base {
      function hook() internal virtual;
      function execute() public { hook(); }
  }

Error Handling


  // require — reverts with message
  require(amount > 0, "Amount must be positive");

  // revert — unconditional revert
  revert("Operation not allowed");

  // assert — checks invariants (uses remaining gas)
  assert(totalSupply == sum(balances));

  // Custom errors (cheaper than revert strings)
  error InsufficientBalance(uint requested, uint available);

  function withdraw(uint amount) public {
      if (amount > balances[msg.sender])
          revert InsufficientBalance(amount, balances[msg.sender]);
      // ...
  }

Gas Optimization Tips


  1. Use calldata instead of memory for read-only parameters
     function process calldata bytes data) external;

  2. Cache storage variables in memory
     uint _count = count;  // One SLOAD
     _count += 1;          // Work in memory
     count = _count;       // One SSTORE

  3. Use unchecked for safe arithmetic
     unchecked { i++; }    // Saves ~100 gas per op

  4. Pack variables into single storage slots
     uint128 a; uint128 b;  // Packed in one slot

  5. Use custom errors instead of require strings
     error Foo();           // Cheaper than revert("Foo")

  6. Short-circuit conditions
     if (a != 0 && b / a > 10)  // Check cheap first

Security Patterns


  // Reentrancy Guard
  bool private locked;
  modifier nonReentrant() {
      require(!locked);
      locked = true;
      _;
      locked = false;
  }

  // Checks-Effects-Interactions Pattern
  function withdraw() public nonReentrant {
      uint amount = balances[msg.sender];    // Check
      balances[msg.sender] = 0;              // Effect
      (bool ok, ) = msg.sender.call{value: amount}("");
      require(ok);                           // Interaction
  }

  // Pull over Push (payee withdraws)
  mapping(address => uint) public pending;
  function claim() public {
      uint amount = pending[msg.sender];
      pending[msg.sender] = 0;
      payable(msg.sender).transfer(amount);
  }