主页 > imtoken钱包下载苹果 > 理解以太坊合约数据读取流程 | 函数式和区块链编程(二)

理解以太坊合约数据读取流程 | 函数式和区块链编程(二)

imtoken钱包下载苹果 2023-07-06 05:11:30

关于函数式编程:

函数式编程是一种不同于传统的面向对象范式的编程范式。 函数式方向是目前编程语言发展的大方向。 所有新设计的编程语言都或多或少地引入了函数式编程功能。

作者认为,“下一代计算机学科体系”将以函数式编程语言为基础。 因此,对于具有“长期主义”思维的程序员来说,打好函数式编程的基础是很有必要的。

关于本专栏:

本专栏将通过对经典著作的实战代码解析和解读,分享作者对函数式编程和区块链的思考和实践。 目前以太坊rpc节点,本专栏将基于两种函数式语言:Rust 和 Elixir,偶尔会参考其他语言作为辅助参考。

关于泰尚:

太上是笔者团队近期实践的一个功能+区块链项目。

太上炼金炉在不改变原有NFT合约的基础上,通过新增“通证合约”,赋予NFT组合、拆解、生命周期、权益绑定的能力,打造NFT+,创造无限创新玩法和想象空间。

项目地址:

愿景0x01:帮助所有NFT及相关项目,让他们拥有无限的商业想象和无限的玩法。

愿景0x02:成为下一代区块链基础设施

Taishang 是这个系列中第一个探索函数式编程的项目。

本文是该系列的第二篇。 通过描述基于Elixir和以太坊的交互方式来描述以太坊的数据读取过程。 需要说明的是,虽然名字是以太坊,但是这个过程适用于任何支持EVM的区块链,比如:FISCO BCOS、Moobeam。

以太坊全节点钱包_以太坊rpc节点_以太坊与以太基金

Ethereumex 和 ExABI

在 Elixir 中,使用了两种 Repos,一种是 Ethereumex:

以太坊区块链的 Elixir JSON-RPC 客户端。

另一个是 ExABI:

Solidity 的 Application Binary Interface[1](ABI) 描述了如何将二进制数据转换为 Solidity 编程语言可以理解的类型。

小Tips:

ABI 是与 EVM 上的合约进行交互的标准方法。 .abi文件包含函数接口描述和事件描述以太坊rpc节点,以json形式呈现。

Hello World合约的ABI如下:

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

需要注意的是,我们在引入Ethereumex时,除了常规写入mix.exs的deps外,还需要在应用程序中挂载:

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

您还需要在 config.exs 中设置访问的区块链节点 url:

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

以太坊rpc节点_以太坊与以太基金_以太坊全节点钱包

交易结构

在Elixir中,我们可以通过代码简单明了的理解结构体(Struct)。

我们可以使用以下 Elixir 结构表示以太坊中的交易:

%Transaction{  nonce: nonce, # 确保交易顺序的累加器  gas_price: @gas.price, # gas 费用  gas_limit: @gas.limit, # gas 上限  to: bin_to, # Binary 形式的地址  value: 0, # 要发送的以太币  init: <<>>, # 机器码  data: data # 要发送给to地址的数据}

需要注意的是,我们现在只是做数据读取,所以不需要nonce参数,只有在写操作的时候才需要改变nonce参数。

eth_call

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

参数

1. Object-交易调用对象

•from:DATA, 20 Bytes -(可选)发送交易的地址。 •to:DATA, 20 Bytes——交易指向的地址。 •gas:QUANTITY-(可选)为交易执行提供的gas 整数。 eth_call 消耗零气体,但某些执行可能需要此参数。 gasPrice:QUANTITY-(可选)用于每个付费 gas 值的 gasPrice 的整数:QUANTITY-(可选)与此交易一起发送的值的整数•data:DATA-(可选)方法签名和编码参数的哈希值。 有关详细信息,请参阅 Solidity 文档中的以太坊合约 ABI [2]

1. QUANTITY|TAG-整数区块号,或字符串“latest”、“earliest”或“pending”,见默认区块参数[3]

回报

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

以太坊全节点钱包_以太坊rpc节点_以太坊与以太基金

例子

——

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

在 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, // 即 gas limit        gas_price: None,        value: Some(U256::from(10000)),        data: None,        nonce: None,        condition: None    };

我们现在只想把流程跑一遍,就不用管gas_price和gas_limit了,直接写死:

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

好了,现在只需要获取2个参数:to和data。

地址的二进制转换

在上一讲中,作者讨论了二进制的基础知识。 同理,我们日常使用的区块链中的地址,比如0x769699506f972A992fc8950C766F0C7256Df601f,可以转化为Binary:

@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

从智能合约功能到数据

通过“函数字符串标识符”和参数列表(params list)生成数据:

以太坊rpc节点_以太坊与以太基金_以太坊全节点钱包

@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" <> payloadend

函数字符串标识符的示例:

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

简单的说就是“函数名(参数1类型,参数2类型,...)”。

我们可以跳转到过去看看encode函数的实现:

def encode(function_signature, data, data_type \\ :input)
# 在这一步会把 string 格式的 function 解析为 function_selector# 然后再次调用 encode 方法,传入 function_selectordef 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

函数选择器结构:

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最终负责将data、function_selector和data_type编译成data_type,见:

返回数据的转换

调用合约时返回的数据需要从十六进制数据转换成对应的格式,所以我们需要写一个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判断返回值:

以太坊全节点钱包_以太坊rpc节点_以太坊与以太基金

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

合约调用函数——Elixir

现在只是最后一步! 我们只需要将以上函数放在一个调用函数中,区块链数据的读取就完成了。

以get_balance函数为例:

@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(%{ # 交易结构被Ethereumex 封装过了! data: data, to: contract_addr })
TypeTranslator.data_to_int(balance_hex)end

合约调用函数—Rust

最后是一个使用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::_, _,_>("get", (), None, Options::default(), None).await?; println!("{}", result);
Ok(())}

请参阅此示例的完整项目:

参考

[1]应用程序二进制接口:

[2] Solidity文档中的以太坊合约ABI:

[3] 默认块参数:#the-default-block-parameter