标准接口检测

本文档提出了 SRC-5 标准,旨在规范智能合约接口的发布和检测方法。它定义了接口的识别方式、合约发布其实现的接口的方式,以及如何检测合约是否实现了 SRC-5 或任何给定接口。该标准受到 ERC-165 的启发,目标是确保声明实现给定接口的所有合约在调用者面前的行为方式一致。

简单总结

一种发布和检测智能合约实现哪些接口的标准方法。 灵感来源于 ERC-165

摘要

此标准规定:

  1. 如何识别接口。
  2. 合约如何发布其实现的接口。
  3. 如何检测合约是否实现了 SRC-5。
  4. 如何检测合约是否实现了任何给定的接口。

动机

对于某些“标准接口”,例如 ERC-721 token 接口,有时查询合约是否支持该接口以及接口的哪个版本非常有用,以便调整与合约交互的方式。本提案标准化了接口的概念,并标准化了接口的识别(命名)。

规范

本文档中的关键词“MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY”和“OPTIONAL”应按照 RFC 2119 中的描述进行解释。

序列化兼容性

此标准的目标之一是确保所有声明实现了给定接口的合约,对于调用者来说,其行为方式与预期类似。在 Cairo 中,该语言不强制执行 Structs 和 Enums 的编码格式,需要开发人员在这些类型是外部函数签名的一部分时,在这些类型上实现 Serde trait,并且不同的实现可能会导致暴露相同接口的合约之间不兼容。

我们定义,为了符合此标准,用作外部函数的参数或返回类型的 Structs 和 Enums 应该 实现由 [#derive(Serde)] 属性提供的默认格式,该格式为:Structs 的序列化字段的连接,以及作为变体标识符的 felt252 和 Enums 的序列化值的连接。其他类型 必须 使用语言核心库中指定的序列化格式。

接口

接口是一组具有具体类型参数的函数签名,通常由 trait 表示。这些旨在由符合该接口的合约实现为 external。例如:

trait IMyContract {
    fn foo(some: u256) -> felt252;
}
泛型类型

从 Cairo 2.0 开始,我们可以利用泛型类型定义 traits 来表示一组接口:

##[starknet::interface]
trait IMyContract<TContractState, TNumber> {
    fn foo(self: @TContractState, some: TNumber) -> felt252;
}

请注意,这些 traits 不代表特定合约的实际公共接口,而代表它们的某种类别。泛型 TContractState 类型 self 参数不包含在函数的公开 API 中。它在内部用于限制函数如何访问本地存储,并且不是函数的公共 API 的一部分。TNumber 类型参数必须在实现中具体化,因为合约的外部函数中不允许使用泛型类型参数。

对于此标准,用 #[starknet::interface] 属性注释的泛型 traits 表示一组接口,而每个实际接口可以表示为非泛型 trait,如上所示。

扩展函数选择器

在 Starknet 中,函数选择器是函数名称(ASCII 编码)的 starknet_keccak。对于此标准,我们将扩展函数选择器定义为函数签名的 starknet_keccak,该签名采用以下格式:

fn_name(param1_type,param2_type,...)->output_type

其中 fn_name 是函数名称,paramN_type 是第 n 个函数参数的类型,output_type 是返回值的类型。

具有零参数且没有返回值的函数的签名是:

fn_name()

类型是在 corelib 中定义的类型(例如:type felt252)。元组、结构体和枚举被视为特殊类型。例如,u256 表示为 (u128,u128),其中 u128 是一种类型,而 u256 是一种结构体。

特殊类型(元组、结构体和枚举)

如何为签名提供这些参数以获取 扩展函数选择器 的定义:

元组

具有 n 个元素的元组的签名是:(elem1_type,elem2_type,...),其中 elemN_type 是第 n 个元组成员的类型。

结构体

具有 n 个字段的结构体的签名是:(field1_type,field2_type,...),其中 fieldN_type 是第 n 个结构体字段的类型。

枚举

具有 n 个字段的枚举的签名是:E(variant1_type,variant2_type,...),其中 variantN_type 是第 n 个枚举变体的类型。

前导 E 避免了与使用元组或结构体的类似签名发生冲突。

例子

  1. 从 Cairo 函数:
##[derive(Drop, Serde)]
enum MyEnum {
    FirstVariant: (felt252, u256),
    SecondVariant: Array<u128>,
}

##[derive(Drop, Serde)]
struct MyStruct {
    field1: MyEnum,
    field2: felt252,
}

fn foo(param1: @MyEnum, param2: MyStruct) -> bool;

签名是:

foo(@E((felt252,(u128,u128)),Array<u128>),(E((felt252,(u128,u128)),Array<u128>),felt252))->E((),())

如何识别接口

对于此标准,我们将接口标识符定义为 接口 中所有 扩展函数选择器 的 XOR。此代码示例显示了如何计算接口标识符:

从这个 Cairo 接口:

struct Call {
    to: ContractAddress,
    selector: felt252,
    calldata: Array<felt252>
}

trait IAccount {
    fn supports_interface(felt252) -> bool;
    fn is_valid_signature(felt252, Array<felt252>) -> bool;
    fn __execute__(Array<Call>) -> Array<Span<felt252>>;
    fn __validate__(Array<Call>) -> felt252;
    fn __validate_declare__(felt252) -> felt252;
}

这是计算接口 ID 的 Python 代码:

## pip install cairo-lang
from starkware.starknet.public.abi import starknet_keccak

## 这些是公共接口函数签名
extended_function_selector_signatures_list = [
    'supports_interface(felt252)->E((),())',
    'is_valid_signature(felt252,Array<felt252>)->E((),())',
    '__execute__(Array<(ContractAddress,felt252,Array<felt252>)>)->Array<(@Array<felt252>)>',
    '__validate__(Array<(ContractAddress,felt252,Array<felt252>)>)->felt252',
    '__validate_declare__(felt252)->felt252'
]

def main():
    interface_id = 0x0
    for function_signature in extended_function_selector_signatures_list:
        function_id = starknet_keccak(function_signature.encode())
        interface_id ^= function_id
    print('IAccount ID:')
    print(hex(interface_id))

if __name__ == "__main__":
    main()
工具

src5-rs 是一种实用程序,用于从 Cairo traits 生成 SRC5 接口 ID,使用 Cairo 源代码作为输入。

合约如何发布它实现的接口

符合 SRC-5 的合约应实现以下接口(称为 ISRC5.cairo):

trait ISRC5 {
    /// @notice 查询合约是否实现了接口
    /// @param interface_id 接口标识符,如 SRC-5 中指定
    /// @return 如果合约实现了 `interface_id`,则为 `true`,否则为 `false`
    fn supports_interface(interface_id: felt252) -> bool;
}

此接口的接口标识符为 0x3f918d17e5ee77373b56385708f855659a07f75997f365cf87748628532a055。你可以通过运行 starknet_keccak('supports_interface(felt252)->E((),())') 来计算它。请注意,bool 的返回类型表示为 E((),()),因为它是在 corelib 中定义的枚举。

因此,实现合约将具有一个 supports_interface 函数,该函数返回:

  • interface_id0x3f918d17e5ee77373b56385708f855659a07f75997f365cf87748628532a055 (SNIP-5 接口) 时,返回 true
  • 对于此合约实现的任何其他 interface_id,返回 true
  • 对于任何其他 interface_id,返回 false

此函数 必须 返回一个布尔值。

如何检测合约是否实现了 SRC-5

  1. 源合约使用 entrypoint_selector 作为以下值,对目标地址进行 call_contract_syscall0xfe80f537b66d12a00b6d3c072b44afbb716e78dde5c3f0ef116ee93d3e3283,并且 calldata 作为一个包含以下元素的 Span:0x3f918d17e5ee77373b56385708f855659a07f75997f365cf87748628532a055。这对应于 contract.supports_interface(0x3f918d17e5ee77373b56385708f855659a07f75997f365cf87748628532a055)
  2. 如果调用失败或返回 false,则目标合约未实现 SRC-5。
  3. 否则它实现了 SRC-5。

如何检测合约是否实现了任何给定的接口

  1. 如果你不确定合约是否实现了 SRC-5,请使用上述步骤进行确认。
  2. 如果合约未实现 SRC-5,那么你将不得不使用传统方式查看合约使用了哪些方法。
  3. 如果合约实现了 SRC-5,则调用 supports_interface(interface_id) 以确定合约是否实现了你可以使用的接口。

版权

版权及相关权利通过 MIT 放弃。

  • 原文链接: github.com/starknet-io/S...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
starknet-io
starknet-io
江湖只有他的大名,没有他的介绍。