#substreams #firehose #ethereum #thegraph #helper #streamingfast #api-bindings

substreams-ethereum

Ethereum链的Substreams开发套件,包含Firehose块模型和辅助工具以及Ethereum ABI编码/解码的实用程序

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日

魔法豆子

Download history 328/week @ 2024-05-01 334/week @ 2024-05-08 324/week @ 2024-05-15 434/week @ 2024-05-22 479/week @ 2024-05-29 405/week @ 2024-06-05 304/week @ 2024-06-12 279/week @ 2024-06-19 235/week @ 2024-06-26 239/week @ 2024-07-03 228/week @ 2024-07-10 299/week @ 2024-07-17 260/week @ 2024-07-24 332/week @ 2024-07-31 296/week @ 2024-08-07 214/week @ 2024-08-14

每月下载次数
用于substreams-helper

Apache-2.0

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