Understanding Ethereum Contract Data Reading Process: Functional Programming & Blockchain (Part 2)

·

Introduction to Functional Programming

Functional programming is a paradigm distinct from traditional object-oriented programming. It represents the future direction of programming language design, with newly developed languages increasingly incorporating functional features.

The author believes that the "next-generation computer science framework" will be built upon functional programming languages. Thus, mastering functional programming fundamentals is essential for programmers with a long-term perspective.

About This Series

This series shares the author's insights and practices on functional programming and blockchain through hands-on code analysis and classic literature interpretation. Currently, it focuses on two functional languages: Rust and Elixir, occasionally referencing others for supplementary context.

Introducing TaiShang

TaiShang is a recent functional + blockchain project by the author's team.

The TaiShang Alchemy Furnace enhances NFTs by adding "proof contracts" without modifying original NFT contracts, enabling features like combination, decomposition, lifecycle management, and rights binding—unlocking limitless innovation possibilities.

Vision 0x01: Empower all NFT-related projects with boundless commercial potential and gameplay.

Vision 0x02: Become next-generation blockchain infrastructure.

TaiShang serves as the first case study for exploring functional programming in this series.

This second installment demonstrates Ethereum data reading via Elixir-Ethereum interactions. Note: While Ethereum-focused, this process applies to all EVM-compatible blockchains (e.g., FISCO BCOS, Moobeam).

Ethereumex and ExABI

Two key Elixir repositories are used:

  1. Ethereumex:
    https://github.com/mana-ethereum/ethereumex
    An Elixir JSON-RPC client for Ethereum.
  2. ExABI:
    https://github.com/poanetwork/ex_abi
    Implements Solidity's Application Binary Interface (ABI), which defines how binary data maps to Solidity types.

    Tip:
    ABI is the standard for interacting with EVM contracts. .abi files describe functions/events in JSON. Example:

    [{
      "constant": true,
      "inputs": [],
      "name": "get",
      "outputs": [{"type": "string"}],
      "payable": false,
      "stateMutability": "view",
      "type": "function"
    }]

Setup

Add Ethereumex to mix.exs:

# mix.exs
def application do
  [extra_applications: [:logger, :ethereumex]]
end

defp deps do
  [{:ethereumex, "~> 0.7.0"}]
end

Configure node URL in config.exs:

config :ethereumex, url: "http://localhost:8545"

Transaction Structure in Elixir

Ethereum transactions can be represented as Elixir structs:

%Transaction{
  nonce: nonce,        # Transaction counter (optional for read-only)
  gas_price: @gas.price, # Fixed gas price
  gas_limit: @gas.limit, # Gas cap
  to: bin_to,         # Binary address
  value: 0,           # ETH to send (0 for reads)
  data: data          # Encoded function call
}

Note: nonce changes only for write operations.

eth_call Overview

The eth_call RPC method executes read-only calls without on-chain transactions:

{
  "jsonrpc": "2.0",
  "method": "eth_call",
  "params": [{
    "to": "0xCONTRACT_ADDRESS",
    "data": "0xENCODED_FUNCTION"
  }, "latest"],
  "id": 1
}

(Source: Ethereum Wiki)

Address Conversion

Convert standard addresses (e.g., 0x7696...601f) to binary:

@spec addr_to_bin(String.t()) :: binary()
def addr_to_bin(addr_str) do
  addr_str
  |> String.replace("0x", "")
  |> Base.decode16!(case: :mixed)
end

Generating Call Data

Encode function calls using ABI:

@spec get_data(String.t(), list()) :: String.t()
def get_data(func_str, params) do
  "0x" <> (
    func_str
    |> ABI.encode(params)
    |> Base.encode16(case: :lower)
  )
end

Function strings follow functionName(type1,type2) format, e.g., "balanceOf(address)".

Decoding Responses

Convert hex responses to usable formats:

defmodule TypeTranslator do
  def data_to_int(raw) do
    raw
    |> hex_to_bin()
    |> ABI.TypeDecoder.decode_raw([{:uint, 256}])
    |> List.first()
  end

  def data_to_str(raw), do: ... # Similar for strings/addresses
end

Full Elixir Contract Call Example

Fetching token balances:

@spec balance_of(String.t(), String.t()) :: integer()
def balance_of(contract_addr, addr_str) do
  {:ok, addr_bytes} = TypeTranslator.hex_to_bytes(addr_str)
  data = get_data("balanceOf(address)", [addr_bytes])
  
  {:ok, balance_hex} = Ethereumex.HttpClient.eth_call(%{
    to: contract_addr,
    data: data
  })
  
  TypeTranslator.data_to_int(balance_hex)
end

Rust Equivalent (web3.rs)

use web3::{contract::Contract, types::H160};

#[tokio::main]
async fn main() -> web3::Result<()> {
    let http = web3::transports::Http::new("https://ropsten.infura.io/v3/YOUR_KEY")?;
    let web3 = web3::Web3::new(http);
    
    let contract = Contract::from_json(
        web3.eth(),
        H160::from_slice(&hex!("7Ad11de6d4C3DA366BC929377EE2CaFEcC412A10")),
        include_bytes!("contract.abi.json")
    )?;
    
    let result: String = contract.query("get", (), None, Options::default(), None).await?;
    println!("{}", result);
    Ok(())
}

(Full example: eth-interactor-rs)

FAQ

1. Why use functional programming for blockchain interactions?

Functional programming emphasizes immutability and pure functions—qualities that align perfectly with blockchain's deterministic execution requirements.

2. How does ABI encoding work?

ABI encoding converts human-readable function calls into EVM-understandable binary data by:

  1. Hashing the function signature to get the 4-byte selector
  2. Packing arguments according to their types

3. What's the gas cost for eth_call?

eth_call consumes zero gas as it executes locally without mining. However, some nodes may impose rate limits.

4. Can I use these methods with Binance Smart Chain?

Absolutely! Any EVM-compatible chain (BSC, Polygon, etc.) uses identical interaction patterns—just change the RPC endpoint.

5. How do I handle dynamic return types?

Use pattern matching in Elixir or enums in Rust to process different ABI output types (e.g., (uint256,string)).


👉 Explore advanced Ethereum development tools