Labs ICT
Pro Login

Solidity Advanced

Inheritance, modifiers, and gas optimization

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);
  }