The Series will focus on two functional programming languages: Rust&Elixir. I would like to share the thinking and practices of functional programming. Maybe I will reference other PL for auxiliary:). I am going to show the function of reading Ethereum Smart Contract by Elixir&Rust in this article. It's important that the program is not only working on Ethereum but also any blockchain that is supporting EVM, for example, @_@ Moonbeam on Polkadot! 0x01 Ethereumex & ExABI The two repos of Elixir I prefer is : Elixir JSON-RPC client for the Ethereum blockchain. Ethereumex & : The (ABI) of Solidity describes how to transform binary data to types which the Solidity programming language understands. ExABI Application Binary Interface Tips for ABI: ABI (Application Binary Interface) in the context of computer science is an interface between two program modules. It is very similar to API (Application Program Interface), a human-readable representation of a code’s interface. ABI defines the methods and structures used to interact with the binary contract, just like API does but on a lower level. —— https://www.quicknode.com/guides/solidity/what-is-an-abi The file is including the description of function interfaces and events by . .abi json This is an example ABI for HelloWorld.sol [{ "constant": true, "inputs": [], "name": "get", "outputs": [{ "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" }] 0x02 Config for Ethereumex Firstly, let us add Ethereumex to the field of and in ! deps application mix.exs # mix.exs: def application do [ mod: {TaiShang.Application, []}, extra_applications: [:logger, :runtime_tools, :ethereumex] ] end …… defp deps do [ {:ethereumex, "~> 0.7.0"} ] end Then,in , add Ethereum protocol host params to your config file: config/config.exs # config.exs config :ethereumex, url: "http://localhost:8545" # node url 0x03 Tx Struct Show in Elixir It's very easy to understanding Struct in Elixir by the code. The tx of Ethereum showed in Elixir: %Transaction{ nonce: nonce, # counter to ensure the sequence of txs gas_price: @gas.price, # gas fee gas_limit: @gas.limit, # gas gas limit to: bin_to, # addr in binary value: 0, # the eth u are going to send init: <<>>, # bytecode data: data # the data u are going to send } We have just read the data in Ethereum (the writing of data will show in another article), so the nonce is useless. The nonce is needed and changed only when we writing data to the contract. eth_call Executes a new message call immediately without creating a transaction on the blockchain. Parameters - The transaction call object Object : , 20 Bytes - (optional) The address the transaction is sent from from DATA : , 20 Bytes - The address the transaction is directed to to DATA : - (optional) Integer of the gas provided for the transaction execution eth_call consumes zero gas, but this parameter may be needed by some executions gas QUANTITY : - (optional) Integer of the used for each paid gas gasPrice QUANTITY gasPrice : - (optional) Integer of the value sent with this transaction value QUANTITY : - (optional) Hash of the method signature and encoded parameters data DATA - integer block number, or the string , or , see the QUANTITY|TAG "latest" "earliest" "pending" default block parameter For details see Ethereum Contract ABI in the Solidity documentation Returns - the return value of the executed contract. DATA Example // Request curl -X POST --data '{"jsonrpc":"2.0","method":"eth_call","params":[{see above}],"id":1}' // Result { "id":1, "jsonrpc": "2.0", "result": "0x" } —— https://eth.wiki/json-rpc/API The mechanism of gas is not friendly for freshmen, so we can set gas_price and gas_limit to a certain number now: @gas %{price: 0, limit: 300_000} Show in Rust It's a similar struct in Rust: /// from: https://kauri.io/#collections/A%20Hackathon%20Survival%20Guide/sending-ethereum-transactions-with-rust/ let tx = TransactionRequest { from: accounts[0], to: Some(accounts[1]), gas: None, // gaslimit gas_price: None, value: Some(U256::from(10000)), data: None, nonce: None, condition: None }; Now there are two params of tx we should handle: to & data. 0x04 String to Binary for Address The address using in blockchain(such as ) could be translated to binary in Elixir program: 0x769699506f972A992fc8950C766F0C7256Df601f @spec addr_to_bin(String.t()) :: Binary.t() def addr_to_bin(addr_str) do addr_str |> String.replace("0x", "") |> Base.decode16!(case: :mixed) end 0x05 Smart Contract Function to Data We would like to generate data by string style of Ethereum functions and params list: @spec get_data(String.t(), List.t()) :: String.t() def get_data(func_str, params) do payload = func_str |> ABI.encode(params) |> Base.encode16(case: :lower) "0x" <> payload end The examples of "String style of Ethereum functions": @func %{ balance_of: "balanceOf(address)", token_of_owner_by_index: "tokenOfOwnerByIndex(address, uint256)", token_uri: "tokenURI(uint256)", get_evidence_by_key: "getEvidenceByKey(string)", new_evidence_by_key: "newEvidenceByKey(string, string)", mint_nft: "mintNft(address, string)", owner_of: "ownerOf(uint256)" } The abstract of string style of eth function is "function_name(param_type1, param_type2,...)" It's good to go deeper to see the implementation of function! encode def encode(function_signature, data, data_type \\ :input) # string type of function to function_selector # then call encode function again with function_selector def encode(function_signature, data, data_type) when is_binary(function_signature) do function_signature |> Parser.parse!() |> encode(data, data_type) end def encode(%FunctionSelector{} = function_selector, data, data_type) do TypeEncoder.encode(data, function_selector, data_type) end The Struct of FunctionSelector: iex(5)> ABI.Parser.parse!("baz(uint8)") %ABI.FunctionSelector{ function: "baz", input_names: [], inputs_indexed: nil, method_id: nil, returns: [], type: nil, types: [uint: 8] } It's the work of to compile , and translate to . TypeEncoder.encode data function_selector data_type data You can see the details : here 0x06 The Translator of Smart Contract Response It's good to write a to change the hex data to normal data in Elixir for response of Smart Contract: TypeTransalator defmodule Utils.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 raw |> hex_to_bin() |> ABI.TypeDecoder.decode_raw([:string]) |> List.first() end def data_to_addr(raw) do addr_bin = raw |> hex_to_bin() |> ABI.TypeDecoder.decode_raw([:address]) |> List.first() "0x" <> Base.encode16(addr_bin, case: :lower) end …… end The functions we are going to choose is based on the response's type, we can fetch it in ABI: { "constant": true, "inputs": [], "name": "get", "outputs": [{ "name": "", "type": "string" # The response is string! } ], "payable": false, "stateMutability": "view", "type": "function" } 0x07 The Caller in Elixir It's the last step! Just mix the functions above in a mixed-function, the data reading of the smart contract is working! For Example - reading the balance of token: ERC20 @spec balance_of(String.t(), String.t()) :: Integer.t() 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(%{ # the tx is encapsulated by ethereumex. data: data, to: contract_addr }) TypeTranslator.data_to_int(balance_hex) end 0x08 The Caller in Rust The last one is the example that calling ethereum by : rust-web3 extern crate hex; use hex_literal::hex; use web3::{ contract::{Contract, Options}, types::{U256, H160, Bytes}, }; #[tokio::main] async fn main() -> web3::contract::Result<()> { let _ = env_logger::try_init(); let http = web3::transports::Http::new("https://ropsten.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161")?; let web3 = web3::Web3::new(http); let addr_u8 = hex::decode("7Ad11de6d4C3DA366BC929377EE2CaFEcC412A10").expect("Decoding failed"); let addr_h160 = H160::from_slice(&addr_u8); let contra = Contract::from_json( web3.eth(), addr_h160, include_bytes!("../contracts/hello_world.json"), )?; // let acct:[u8; 20] = hex!("f24ff3a9cf04c71dbc94d0b566f7a27b94566cac").into(); let result = contra.query::<String, _, _,_>("get", (), None, Options::default(), None).await?; println!("{}", result); Ok(()) } | The Code Example of Elixir in this article: here The Code Example of Rust in this article: here