本系列將重點介紹兩種函數式編程語言: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: ,
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":[],"id":1}'
// Result
{
"id":1,
"jsonrpc": "2.0",
"result": "0x"
}
gas的機制對新人不友好,所以我們現在可以設置gas_price和gas_limit為一個特定的數字:
@gas %
在 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::,
types::,
};
#[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(())
}
發文者:鏈站長,轉載請註明出處:https://www.jmb-bio.com/4172.html