35个版本
新版本 0.9.13 | 2024年8月20日 |
---|---|
0.9.11 | 2024年4月22日 |
0.9.10 | 2024年3月25日 |
0.9.9 | 2023年12月19日 |
0.1.4 | 2022年6月22日 |
魔法豆子
每月下载次数
用于substreams-helper
225KB
代码行数(不包括注释)
Substreams Ethereum
Substreams开发套件用于Ethereum链,包含Rust Firehose块模型和辅助工具以及Ethereum ABI编码/解码的实用程序。
使用方法
[package]
name = "substreams-acme"
version = 0.1.2
[lib]
crate-type = ["cdylib"]
[dependencies]
substreams-ethereum = "0.6.0"
开发
手动同步渲染的Rust Firehose块模型与实际的Protocol Buffer定义文件。
这意味着对Protocol Buffer文件所做的更改必须手动重新生成并提交到Git中。
重新生成Rust Firehose块模型从Protocol Buffer
./gen.sh
注意
ABI与元组
元组现在支持ABI生成代码。如果您更喜欢使用“结构体”而不是“元组”来表示事件/函数,则可以使用以下说明手动生成代码。
说明
第一步将是略微修改ABI,以确保生成的解码代码正确。然后,将复制并修改初始生成的代码,以便正确解码元组。
想法是将ABI内部的tuple
“分解”成它自己的“事件”,这将生成表示元组的struct
的代码以及该struct
本身的解码代码。然后我们将调整生成的代码,将一切连接起来。
从
[
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "bytes32",
"name": "orderHash",
"type": "bytes32"
},
{
"components": [
{
"internalType": "enum ItemType",
"name": "itemType",
"type": "uint8"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"indexed": false,
"internalType": "struct SpentItem[]",
"name": "offer",
"type": "tuple[]"
}
],
"name": "OrderFulfilled",
"type": "event"
}
]
我们将SpentItem
分解成它自己的类型,并将offer
类型从tuple[]
替换为address[]
,使其能够正确编译
[
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "bytes32",
"name": "orderHash",
"type": "bytes32"
},
{
"components": [
{
"internalType": "enum ItemType",
"name": "itemType",
"type": "uint8"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"indexed": false,
"internalType": "struct SpentItem[]",
"name": "offer",
"type": "address[]"
}
],
"name": "OrderFulfilled",
"type": "event"
},
{
"anonymous": false,
"name": "SpentItem",
"type": "event",
"inputs": [
{
"internalType": "enum ItemType",
"name": "itemType",
"type": "uint8"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
]
}
]
注意无需删除
components
或更改internalType
的值,它们将被忽略。
使用修改后的ABI执行cargo build
,以便在src/abi/<file>.rs
中生成代码,它现在是不正确的,但我们打算将其复制到另一个地方并使其工作。
在src/abi/<file>.rs
中找到OrderFulfilled
事件的生成代码,并将其复制到新的文件src/events.rs
中。你应该复制以下内容:pub struct OrderFulfilled
块,impl OrderFulfilled
块,以及impl substreams_ethereum::Event for OrderFulfilled
块,SpentItem
结构和impl SpentItem
块
#[derive(Debug, Clone, PartialEq)]
pub struct OrderFulfilled {
pub order_hash: [u8; 32usize],
pub offer: Vec<Vec<u8>>,
}
impl OrderFulfilled {
const TOPIC_ID: [u8; 32] = [
227u8,
56u8,
222u8,
32u8,
39u8,
120u8,
0u8,
226u8,
120u8,
84u8,
168u8,
160u8,
171u8,
38u8,
80u8,
66u8,
198u8,
237u8,
193u8,
186u8,
154u8,
14u8,
209u8,
73u8,
102u8,
185u8,
47u8,
163u8,
179u8,
98u8,
194u8,
244u8,
];
pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool {
if log.topics.len() != 1usize {
return false;
}
if log.data.len() < 96usize {
return false;
}
return log.topics.get(0).expect("bounds already checked").as_ref()
== Self::TOPIC_ID;
}
pub fn decode(
log: &substreams_ethereum::pb::eth::v2::Log,
) -> Result<Self, String> {
let mut values = ethabi::decode(
&[
ethabi::ParamType::FixedBytes(32usize),
ethabi::ParamType::Array(
Box::new(ethabi::ParamType::Address),
),
],
log.data.as_ref(),
)
.map_err(|e| format!("unable to decode log.data: {:?}", e))?;
values.reverse();
Ok(Self {
order_hash: {
let mut result = [0u8; 32];
let v = values
.pop()
.expect(INTERNAL_ERR)
.into_fixed_bytes()
.expect(INTERNAL_ERR);
result.copy_from_slice(&v);
result
},
offer: values
.pop()
.expect(INTERNAL_ERR)
.into_array()
.expect(INTERNAL_ERR)
.into_iter()
.map(|inner| {
inner.into_address().expect(INTERNAL_ERR).as_bytes().to_vec()
})
.collect(),
})
}
}
impl substreams_ethereum::Event for OrderFulfilled {
const NAME: &'static str = "OrderFulfilled";
fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool {
Self::match_log(log)
}
fn decode(
log: &substreams_ethereum::pb::eth::v2::Log,
) -> Result<Self, String> {
Self::decode(log)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SpentItem {
pub item_type: substreams::scalar::BigInt,
pub amount: substreams::scalar::BigInt,
}
impl SpentItem {
const TOPIC_ID: [u8; 32] = [
18u8,
7u8,
103u8,
62u8,
30u8,
101u8,
94u8,
85u8,
209u8,
209u8,
166u8,
82u8,
139u8,
137u8,
197u8,
45u8,
11u8,
224u8,
230u8,
74u8,
27u8,
234u8,
238u8,
52u8,
150u8,
245u8,
214u8,
202u8,
230u8,
104u8,
138u8,
22u8,
];
pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool {
if log.topics.len() != 1usize {
return false;
}
if log.data.len() != 64usize {
return false;
}
return log.topics.get(0).expect("bounds already checked").as_ref()
== Self::TOPIC_ID;
}
pub fn decode(
log: &substreams_ethereum::pb::eth::v2::Log,
) -> Result<Self, String> {
let mut values = ethabi::decode(
&[
ethabi::ParamType::Uint(8usize),
ethabi::ParamType::Uint(256usize),
],
log.data.as_ref(),
)
.map_err(|e| format!("unable to decode log.data: {:?}", e))?;
values.reverse();
Ok(Self {
item_type: {
let mut v = [0 as u8; 32];
values
.pop()
.expect(INTERNAL_ERR)
.into_uint()
.expect(INTERNAL_ERR)
.to_big_endian(v.as_mut_slice());
substreams::scalar::BigInt::from_unsigned_bytes_be(&v)
},
amount: {
let mut v = [0 as u8; 32];
values
.pop()
.expect(INTERNAL_ERR)
.into_uint()
.expect(INTERNAL_ERR)
.to_big_endian(v.as_mut_slice());
substreams::scalar::BigInt::from_unsigned_bytes_be(&v)
},
})
}
}
impl substreams_ethereum::Event for SpentItem {
const NAME: &'static str = "SpentItem";
fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool {
Self::match_log(log)
}
fn decode(
log: &substreams_ethereum::pb::eth::v2::Log,
) -> Result<Self, String> {
Self::decode(log)
}
}
目前,此src/events.rs
文件未编译/包含在项目中,因为在Rust中,只有当模块“定义”在某处时才包含模块,因此让我们定义该模块。在src/lib.rs
文件顶部,添加以下内容:
mod events
让我们开始修改我们的错误生成的代码,使其正确。首先定义INTERNAL_ERR
常量,因为它通常被使用,将其放在src/events.rs
文件的顶部
const INTERNAL_ERR: &str = "decode event internal error";
现在是一个棘手的部分,我们需要更新TOPIC_ID
常量,因为它现在是不正确的。如果您已经知道事件ID(它是topics #0
),那就太好了。如果您不知道,您可以通过对事件定义进行keccak256
哈希来计算它。您需要事件名称及其类型来计算它,在我们的情况下是OrderFulfilled(bytes32,(uint8,uint256)[])
警告事件定义必须不带空格或额外标点符号,任何错误的字符都将使整个事件ID错误。
您可以使用 jq -'.[] | select(.name == "OrderFulfilled") | .inputs[].type' | tr "\n" ","
和 jq -'.[] | select(.name == "SpentItem") | .inputs[].type' | tr "\n" ","
命令在刚刚修改的 "modified" ABI 文件上获取正确的有序类型。然后正确地重新组装它,将 OrderFulfilled
的第二个字段更改为元组定义 (uint8,uint256)[]
(由于我们的修改,它返回为 address[]
)。
现在我们有了事件定义,我们可以计算 keccak256 哈希。我们使用 CLI 工具 keccak-256sum
来完成此操作(《[安装说明](https://gist.github.com/miguelmota/60259aed8ce95477131c0a1f4f31e0da)》)
$ printf 'OrderFulfilled(bytes32,(uint8,uint256)[])' | keccak-256sum
e86f4727db138d4b9cb776888b1d2239562eafaa38dd110b7d5def7698ccfd41 -
因此,我们的事件主题 0 是 e86f4727db138d4b9cb776888b1d2239562eafaa38dd110b7d5def7698ccfd41
。现在我们只需要将 TOPIC_ID
常量定义在 impl OrderFulfilled
定义中更改为
const TOPIC_ID: [u8; 32] = hex_literal::hex!("e86f4727db138d4b9cb776888b1d2239562eafaa38dd110b7d5def7698ccfd41");
现在,在 pub struct OrderFulfilled
中,更改 offer
字段(目前定义为 offer: Vec<Vec<u8>>
),它通常持有数组元组,更改为正确的值 offer: Vec<SpentItem>
pub struct OrderFulfilled {
pub order_hash: [u8; 32usize],
pub offer: Vec<SpentItem>,
}
然后找到 pub fn decode(log: <type>)
中的事件 "token" 定义,该定义目前是
let mut values = ethabi::decode(
&[
ethabi::ParamType::FixedBytes(32usize),
ethabi::ParamType::Array(Box::new(ethabi::ParamType::Address)),
],
log.data.as_ref(),
)
确定字段是元组,在我们的例子中是第二个字段,并将其定义为 ethabi::ParamType::Tuple
类型。
let mut values = ethabi::decode(
&[
ethabi::ParamType::FixedBytes(32usize),
ethabi::ParamType::Array(Box::new(ethabi::ParamType::Tuple(vec![
ethabi::ParamType::Uint(8usize),
ethabi::ParamType::Uint(256usize),
]))),
],
log.data.as_ref(),
信息 函数
SpentItem::decode
在开头定义了一个let mut values
变量,用于列出用于元组的正确元素,无需手动定义列表,只需复制即可。
现在仍然在 OrderFulfilled
函数中,找到实际的解码工作代码段 offer:
,目前看起来像
...
},
offer: values
.pop()
.expect(INTERNAL_ERR)
.into_array()
.expect(INTERNAL_ERR)
.into_iter()
.map(|inner| {
inner
.into_address()
.expect(INTERNAL_ERR)
.as_bytes()
.to_vec()
})
.collect(),
修改它,使其将解码工作转发给 SpentItem
结构
...
},
offer: values
.pop()
.expect(INTERNAL_ERR)
.into_array()
.expect(INTERNAL_ERR)
.into_iter()
.map(|inner| {
let fields = inner.into_tuple().expect(INTERNAL_ERR);
SpentItem::decode(fields).expect(INTERNAL_ERR);
})
.collect(),
在 impl OrderFulfilled
结构中的最终修改是稍微修改 pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool
定义。它包含一段代码,确保 log.data
有一定数量的字节,我们修改后的 ABI 将为 log.data
生成错误的验证代码,所以让我们去掉它
pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool {
if log.topics.len() != 1usize {
return false;
}
if log.data.len() < 96usize {
return false;
}
return log.topics.get(0).expect("bounds already checked").as_ref() == Self::TOPIC_ID;
}
应变为
pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool {
if log.topics.len() != 1usize {
return false;
}
return log.topics.get(0).expect("bounds already checked").as_ref() == Self::TOPIC_ID;
}
注意 计算过于繁琐,无法手动执行,主题数量和主题 0 值的验证足以匹配所需的仅有的事件。
现在,让我们将注意力转移到 SpentItem
的实现上。在 impl SpentItem
块中,删除
TOPIC_ID
常量match_log
函数
并且完全删除 impl substreams_ethereum::Event for SpentItem
块。最后要做的就是通过更改当前签名来调整 SpentItem::decode
函数
pub fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result<Self, String>
使其接受一个 Vec<ethabi::Token>
,并将变量重命名为 values
,同时将其设置为可变的
pub fn decode(mut values: Vec<ethabi::Token>) -> Result<Self, String>
最后,移除函数体中现有的 let mut values
定义,它看起来像
let mut values = ethabi::decode(
&[
ethabi::ParamType::Uint(8usize),
ethabi::ParamType::Uint(256usize),
],
log.data.as_ref(),
)
.map_err(|e| format!("unable to decode log.data: {:?}",
注意 您必须保留下面的
values.reverse()
部分,这对于解码代码的正确运行非常重要。
现在一切都已经完成,您可以使用 OrderFulfilled
来解码包含 tuple
的事件。最终的代码看起来像这样
const INTERNAL_ERR: &str = "decode event internal error";
#[derive(Debug, Clone, PartialEq)]
pub struct OrderFulfilled {
pub order_hash: [u8; 32usize],
pub offer: Vec<SpentItem>,
}
impl OrderFulfilled {
const TOPIC_ID: [u8; 32] =
hex_literal::hex!("e86f4727db138d4b9cb776888b1d2239562eafaa38dd110b7d5def7698ccfd41");
pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool {
if log.topics.len() != 1usize {
return false;
}
return log.topics.get(0).expect("bounds already checked").as_ref() == Self::TOPIC_ID;
}
pub fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result<Self, String> {
let mut values = ethabi::decode(
&[
ethabi::ParamType::FixedBytes(32usize),
ethabi::ParamType::Array(Box::new(ethabi::ParamType::Tuple(vec![
ethabi::ParamType::Uint(8usize),
ethabi::ParamType::Uint(256usize),
]))),
],
log.data.as_ref(),
)
.map_err(|e| format!("unable to decode log.data: {:?}", e))?;
values.reverse();
Ok(Self {
order_hash: {
let mut result = [0u8; 32];
let v = values
.pop()
.expect(INTERNAL_ERR)
.into_fixed_bytes()
.expect(INTERNAL_ERR);
result.copy_from_slice(&v);
result
},
offer: values
.pop()
.expect(INTERNAL_ERR)
.into_array()
.expect(INTERNAL_ERR)
.into_iter()
.map(|inner| {
let fields = inner.into_tuple().expect(INTERNAL_ERR);
SpentItem::decode(fields).unwrap()
})
.collect(),
})
}
}
impl substreams_ethereum::Event for OrderFulfilled {
const NAME: &'static str = "OrderFulfilled";
fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool {
Self::match_log(log)
}
fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result<Self, String> {
Self::decode(log)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SpentItem {
pub item_type: substreams::scalar::BigInt,
pub amount: substreams::scalar::BigInt,
}
impl SpentItem {
pub fn decode(mut values: Vec<ethabi::Token>) -> Result<Self, String> {
values.reverse();
Ok(Self {
item_type: {
let mut v = [0 as u8; 32];
values
.pop()
.expect(INTERNAL_ERR)
.into_uint()
.expect(INTERNAL_ERR)
.to_big_endian(v.as_mut_slice());
substreams::scalar::BigInt::from_unsigned_bytes_be(&v)
},
amount: {
let mut v = [0 as u8; 32];
values
.pop()
.expect(INTERNAL_ERR)
.into_uint()
.expect(INTERNAL_ERR)
.to_big_endian(v.as_mut_slice());
substreams::scalar::BigInt::from_unsigned_bytes_be(&v)
},
})
}
}
如果您遇到任何问题,请通过 Discord 联系我们,我们将帮助您解决。
依赖项
~7–10MB
~167K SLoC