Rust 和 Elixir 如何读取以太坊和其他 EVM 智能合约:函数式编程和区块链

Rust 和 Elixir 如何读取以太坊和其他 EVM 智能合约:函数式编程和区块链

本系列将重点介绍两种函数式编程语言 :Rust&Elixir;。本篇分享函数式编程的思想和实践。

在这篇文章中将展示 Elixir&Rust; 读取以太坊智能合约的功能。重要的是,该程序不仅在以太坊上工作,而且还在任何支持 EVM 的区块链上工作,例如,Polkadot 上的 Moonbeam !

Ethereumex & ExABI

**
**

我更喜欢 Elixir 的两个存储库是 Ethereumex:用于以太坊区块链的 Elixir JSON-RPC 客户端。

ExABI:Solidity 的应用程序二进制接口 (ABI) 描述了如何将二进制数据转换为 Solidity 编程语言能够理解的类型。

ABI 小贴士 :

ABI(应用程序二进制接口) 是计算机科学中两个程序模块之间的接口。

它与 API(应用程序接口) 非常相似,API 是代码接口的可读表示形式。ABI 定义了用于与二进制合约交互的方法和结构,就像 API 那样,只是在更低的层次上。

.abi 文件包含了 json 格式的函数接口和事件的描述。

这是 HelloWorld.sol 的示例 ABI:

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

Ethereumex 的配置

首先,让我们将 Ethereumex 添加到 mix.exs 中的 depsand 应用领域!

    # mix.exs:      def application do        [          mod: {TaiShang.Application, []},          extra_applications: [:logger, :runtime_tools, :ethereumex]        ]      end      ……      defp deps do          [               {:ethereumex, "~> 0.7.0"}          ]      end

然后 , 在 config/config.exs 中。将以太坊协议主机参数添加到配置文件中 :

    # config.exs      config :ethereumex,        url: "http://localhost:8545" # node url

Tx 结构

**
**

在 Elixir 中显示

通过代码很容易理解 Elixir 中的 Struct。

以太坊的 tx 在 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      }

我们刚刚读取了以太坊中的数据,因此随机数是没有用的。只有在我们把数据写进合约的时候才需要更改随机数。

eth_call

立即执行一个新的消息调用,而不需要在区块链上创建交易。

参数

Object -交易调用对象

from: DATA, 20 Bytes -(可选) 交易发送的地址

to: DATA, 20 Bytes -交易被指向到的地址

gas: QUANTITY -(可选) 为交易执行提供的 gas 整数。eth_call 消耗零 gas,但某些执行可能需要这个参数

gasPrice: QUANTITY -(可选) 每一种付费 gas 使用的 gasPrice 的整数

value: QUANTITY -(可选) 与该交易一起发送的值的整数

data: DATA -(可选) 方法签名和编码参数的哈希值

QUANTITY|TAG -整数区块号,或字符串 "latest", "earliest" 或 "pending",参见默认区块参数

返回

DATA -已执行合约的返回值。

例子

    // Request      curl -X POST --data '{"jsonrpc":"2.0","method":"eth_call","params":[{see above}],"id":1}'      // Result      {        "id":1,        "jsonrpc": "2.0",        "result": "0x"      }

gas 的机制对新人不友好,所以我们现在可以设置 gas_price 和 gas_limit 为一个特定的数字 :

@gas %{price: 0, limit: 300_000}

在 Rust 中显示

它是 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          };

现在我们应该处理 tx 的两个参数 :

to & data。

地址的字符串到二进制

**
**

区块链中使用的地址 (如 0x769699506f972A992fc8950C766F0C7256Df601f) 可以在 Elixir 程序中转换为二进制 :

    @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

智能合约功能到数据

**
**

我们希望通过以太坊函数和参数列表的字符串样式生成数据 :

    @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

“以太坊函数的字符串样式”示例:

    @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)"          }

eth 函数的字符串样式抽象为 "function_name(param_type1, param_type2,…)"

深入了解 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

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]      }

TypeEncoder.encode 的工作是编译数据,function_selector 和 data_type 转换为数据。

智能合约响应的翻译器

在 Elixir 中编写一个 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

我们要选择的函数是基于响应的类型,我们可以在 ABI 中获取它 :

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

Elixir 中的调用者

**
**

这是最后一步!只要把上面的功能混合在一起,智能合约的数据读取就可以工作了 !

例如 : 读取 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

Rust 中的调用者

**
**

最后一个是调用以太坊的例子

    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::("get", (), None, Options::default(), None).await?;          println!("{}", result);          Ok(())      }

Source: https://hackernoon.com/how-rust-and-elixir-read-ethereum-and-other-evm-smart-contracts-functional-programming-and-blockchain

关于

**
**

ChinaDeFi - ChinaDeFi.com 是一个研究驱动的 DeFi 创新组织,同时我们也是区块链开发团队。每天从全球超过 500 个优质信息源的近 900 篇内容中,寻找思考更具深度、梳理更为系统的内容,以最快的速度同步到中国市场提供决策辅助材料。如果您是从业者希望获得更多海外最新技术方案及项目信息,可以查看 Gavin 的“每日文章”笔记 https://day.chinadefi.com (内容可能比较生涩,但是相信可以带来帮助)。

Layer 2 社区 - 我们正在组建专业的 Layer 2 社区,欢迎对 Layer 2 感兴趣的区块链技术爱好者、研究分析人与 Gavin (微信 : chinadefi)联系,共同探讨 Layer 2 带来的落地机遇。同时欢迎加入 ChineDeFi 社区(微信联系:cndefi),敬请关注我们的微信公众号 “去中心化金融社区”