Reactive Contract(睿应层)

概述:

与传统的智能合约(Smart Contracts)不同,传统智能合约只能在用户发起交易时才会执行,而 RC 则能够主动监控 EVM 兼容链上的事件并对此作出反应。RC 会根据合约中实现的逻辑来处理这些事件,并在区块链上自动执行后续操作。这种创新引入了一种去中心化的机制,可以在无需人工干预的情况下自动对链上事件作出响应。

Ethereum 的世界中,智能合约彻底改变了我们对无信任协议执行方式的理解。

传统情况下,智能合约只有在用户主动发送交易时才会被触发执行

这种模式存在一些天然的限制:

  • 智能合约无法自主发起操作
  • 无法在没有外部触发的情况下响应链上的事件

因此必须依赖外部系统,例如:

  • 用户手动发送交易
  • 自动化脚本(例如交易机器人)

但这种方式带来了新的问题:

  • 需要持有私钥
  • 引入中心化控制点

Reactive Contracts(RC)正是为了解决这一限制而设计的。

RC 可以:

  • 自主监听 EVM 中发生的事件
  • 自动触发后续区块链操作

这使得开发者可以实现:

  • 多个链获取信息
  • 不同区块链之间执行逻辑
  • 无需中心化控制

RC 能够在整个区块链生态系统中自动执行复杂逻辑和交易

Reactive Contracts 的核心特征:

  • RC 不仅响应用户的直接交易,还能响应跨 EVM 链的事件。
  • 在事件发生后,它们可以执行链上操作,可能是在同一条链或其他链上

与预言机结合时,RC 可以对 链外事件(由预言机写入链上) 做出实时响应,并执行预先定义好的链上操作。这种 RC 与预言机的协作实现了对数字世界与现实世界的动态互动,大幅拓展了区块链应用的范围和功能。

RC的优势

1. 去中心化(Decentralization)

RC 在区块链上独立运行,消除了中心化控制点。

这样可以通过减少操控或单点故障的风险来提升系统安全性


2. 自动化(Automation)

RC 可以在链上事件发生时自动执行智能合约逻辑

这带来的好处包括:

  • 减少人工干预
  • 提供实时自动响应

3. 跨链互操作性(Cross-Chain Interoperability)

RC 可以与多个区块链交互

这意味着可以实现:

  • 复杂的跨链逻辑
  • 构建连接不同网络的应用

4. 更高的效率与功能性

通过对实时数据作出反应,RC 可以提升智能合约的效率,并支持更多高级功能,例如:

  • 复杂金融工具
  • 动态 NFT
  • 创新的 DeFi 应用

例如在 Ethereum 上运行的 DeFi 协议 Uniswap 就可以通过 RC 实现更自动化的交易逻辑。


5. 推动 DeFi 及更多领域创新

RC 为 DeFi 以及其他区块链应用开启了新的可能性,例如:

  • 自动化交易
  • 动态治理系统

从而打造一个更加响应式和互联的区块链生态系统

RC的劣势

Reactive Contract 无法阻止触发事件的交易,只能在事件发生后执行新的交易来响应或补救。

换句话说,Reactive Contract 不能阻止、取消、修改交易,RC只能监听事件、自动触发交易、自动执行策略。

==Reactive Network 可以理解为一种 去中心化的链上自动化执行网络,其功能类似自动化 bot,但由去中心化节点网络执行。==

RC 与传统智能合约的区别

RC 与传统智能合约的主要区别在于 “反应性(Reactivity)”

传统智能合约是被动的(Passive)

  • 只有在 EOA(Externally Owned Account) 发起交易时才会执行。

Reactive Contracts主动的(Reactive)

  • 持续监控区块链
  • 监听感兴趣的事件
  • 在检测到事件时 自动执行预定义的区块链操作

控制反转(Inversion of Control)

理解 RC 的一个关键概念是 控制反转(IoC)

传统智能合约采用的是 直接控制模型

  • 合约函数的执行由外部参与者触发,例如:
    • 用户(EOA)
    • 自动化机器人(bot)

而 RC 反转了这种控制关系

  • 合约根据预定义事件的发生来决定何时执行。

这种 IoC 模式改变了应用与区块链交互的方式,使系统更加:

  • 动态
  • 自动化
  • 响应式

没有 RC 时的情况

如果没有 Reactive Contract,你通常需要创建一个额外的实体(例如一个机器人 bot):

该 bot 会:

  1. 监控区块链
  2. 使用某些数据服务(通常是中心化的)
  3. 持有私钥
  4. 从自己的 EOA 地址发起交易

这种系统确实有用,但存在一些问题:

  • 需要托管私钥
  • 引入中心化组件
  • 某些场景下并不适合

IoC 的优势

通过 控制反转,我们可以避免运行那些“模拟人类签名交易”的额外实体。

如果你已经定义好了:

某个链上事件发生后需要执行的一系列交易逻辑

那么这些逻辑应该能够 完全去中心化地运行,因为:

  • 输入数据在链上
  • 输出结果也在链上

Reactive Network 为智能合约提供了一个过去一直缺失的重要能力:

自动执行能力

即:

  • 不需要人
  • 不需要 bot
  • 不需要签名交易

只需要 其他链上事件触发即可执行

开发者无需运行 bot 或持有私钥,Reactive Network 节点会自动代表 RC 提交交易。


Reactive Contract 内部发生了什么

创建 Reactive Contract 时,首先需要指定:

  • 要监听的 区块链
  • 要监听的 合约地址
  • 要监听的 事件(topic0)

RC 会持续监控这些地址,当检测到指定事件时就会执行。

这些事件可能包括:

  • ETH 转账
  • Token 转账
  • DEX 交易
  • 借贷
  • 闪电贷
  • 投票
  • 巨鲸转账
  • 任何智能合约活动

事件触发后的流程

当 RC 检测到相关事件后:

Reactive Network 会自动执行 RC 中实现的逻辑。

执行内容可能包括:

  • 根据事件数据进行计算
  • 更新合约状态

RC 是 有状态的(stateful),意味着:

  • 可以存储数据
  • 可以更新数据
  • 可以累积历史信息

因此可以实现:

  • 长期数据统计
  • 基于历史数据 + 新事件的决策逻辑

执行结果

在事件处理过程中:

  1. RC 更新自己的状态
  2. RC 可以在 EVM 链上发起交易

整个过程:

  • Reactive Network 中完成
  • 无需信任
  • 自动执行
  • 快速可靠

事件(Events)与回调(Callbacks)的工作原理

Ethereum 中,事件(Events) 允许智能合约在满足特定条件时记录信息,从而与外部世界进行通信。这使得 去中心化应用(dApps) 能够在某些事件发生时触发响应,而无需持续轮询区块链。事件会被 EVM 进行索引,因此可以非常容易地搜索和过滤。这对于监控区块链活动非常有用,例如:

  • Token 转账
  • 合约状态更新
  • 来自预言机的价格变化

ReactVM 执行环境

Reactive Contracts 在 ReactVM 中运行。

ReactVM 的特点:

  • 私有执行环境
  • 只能与同一部署者部署的合约交互

这样可以确保:

  • 执行环境安全
  • 合约隔离

向目标链发送 Callback

Reactive Contracts 可以通过 Callback 事件 发起跨链交易。

流程:

  1. RC 触发 Callback
  2. Reactive Network 监听该事件
  3. Network 在目标链发送交易

Callback 事件参数

1
2
3
4
5
6
event Callback(
uint256 indexed chain_id,
address indexed _contract,
uint64 indexed gas_limit,
bytes payload
);

Callback 需要提供:

  • chain_id:目标链 ID(EIP-155)
  • _contract:目标合约地址
  • **gas_limit **:目标交易 gas
  • payload:编码后的函数调用数据

Callback 处理过程

例如:

1
2
3
4
5
6
7
8
9
10
11
12
bytes memory payload =
abi.encodeWithSignature(
"onApproval(address,address,address,address,uint256)",
...
);

emit Callback(
SEPOLIA_CHAIN_ID,
address(approval_service),
GAS_LIMIT,
payload
);

流程:

当 Callback 事件被触发后:

Reactive Network 会:

  1. 读取 payload
  2. 解析交易数据
  3. 在目标链提交交易
1
2
3
4
5
6
7
  react()

emit Callback

Reactive Network

目标链执行函数

关于授权的重要说明

为了安全性(防止 RC 伪造地址调用):

Reactive Network 会自动:

RVM ID 替换 payload 中前 160 bit 的参数。

这样可以确保目标合约能够识别调用来源是 ReactVM,而不是用户伪造的地址。

RVM ID:

  • 等价于 ReactVM 地址
  • 与合约部署者地址相同

因此:回调函数的 第一个参数永远是 ReactVM 地址,无论你在 Solidity 中如何命名。

ReactVM 与 Reactive Network —— 双状态环境

Reactive Contracts 在两个独立环境中同时存在,并拥有两个不同的状态。

Reactive Network 与 ReactVM 的区别

每一个 Reactive Contract 都有 两个实例

  1. 一个在 Reactive Network 执行 subscription
  2. 一个在 ReactVM 处理 react()

需要注意:

  • 两个实例都会在每个网络节点上物理存储并运行
  • 两个实例拥有 相同代码
  • 但拥有 不同状态

并行化RC是一个架构决策,旨在确保即使事件数量庞大也能保持高性能。


Reactive Network

Reactive Network 的结构类似于普通的 Ethereum EVM 区块链,但增加了系统合约。

这些系统合约可以:

  • 订阅事件
  • 取消订阅事件

监听来源链,例如:

  • Ethereum
  • BNB Chain
  • Polygon
  • Optimism

执行环境:响应式合约可以使用所有标准的 EVM 功能。然而,它们运行在一个私有的 ReactVM 中,这限制了它们只能与同一部署者部署的合约进行交互。每个部署者地址都会拥有:

一个专属 ReactVM

Reactive Network 会持续监控事件日志,并将这些日志与响应式合约中定义的订阅条件进行匹配。当检测到符合条件的事件时,网络会触发 react() 方法,并将相关的事件信息作为参数传入。


ReactVM

ReactVM 是一个 受限制的虚拟机,专门用于在隔离环境中处理事件。从同一个地址部署的合约会在同一个 ReactVM 中执行。它们可以彼此交互,但不能与 Reactive Network 上的其他合约交互

特点:

  • 用于处理事件
  • 运行环境隔离

规则:

  • 同一地址部署的合约 → 在同一个 ReactVM 中运行
  • 可以互相调用
  • 不能调用 Reactive Network 上其他部署者的合约

ReactVM 与外界交互的两种方式

同一个ReactVM 中的合约可以通过 Reactive Network 与外部世界交互:

1 监听事件

ReactVM 会:

  • 监听已订阅事件
  • 当事件发生时执行代码

2 发起 Callback

根据事件输入执行代码后:

ReactVM 会请求 Reactive Network:

  • 向目标链发送 Callback
  • 执行链上交易

双状态架构

每个 Reactive Contract:

  • 代码相同
  • 状态不同
环境 状态
Reactive Network Network State
ReactVM VM State

因此每个函数必须明确:在哪个环境执行


如何识别执行环境

执行环境由变量 vm 决定。

项目中需要继承AbstractReactive


detectVm() 函数

系统通过检查一个系统合约地址来判断环境:

1
2
3
4
5
function detectVm() internal {
uint256 size;
assembly { size := extcodesize(0x0000000000000000000000000000000000fffFfF) }
vm = size == 0;
}

判断逻辑

检查地址:0x0000000000000000000000000000000000fffFfF,情况:

Reactive Network

1
2
3
该地址存在代码
size > 0
vm = false

ReactVM

1
2
3
该地址没有代码
size = 0
vm = true

限制执行环境

使用 modifier 控制函数执行环境。


Reactive Network 专用函数

1
2
3
4
modifier rnOnly() {
require(!vm, 'Reactive Network only');
_;
}

只允许该函数在Reactive Network执行。


ReactVM 专用函数

1
2
3
4
modifier vmOnly() {
require(vm, 'VM only');
_;
}

只允许该函数在ReactVM执行。


双变量状态管理

Reactive 架构中,每个已部署的合约可以运行在两种不同的运行状态中:

1 Reactive Network State

负责:

  • 与系统合约交互
  • 订阅事件

使用用于注册和管理事件订阅所需的变量和方法,变量来自AbstractReactive,包括:

  • service
  • vm
  • service.subscribe()

2 ReactVM State

负责:

  • 执行业务逻辑
  • 处理事件

变量由合约自己定义。

为了适应这两种状态,需要维护两套概念上的变量集合

  • 一套位于 基础合约(面向网络的合约上下文)
  • 另一套位于 ReactVM 上下文

在这个新的示例中:

  • Reactive Network 的变量来自继承的 AbstractReactive 合约
  • ReactVM 的变量则是在响应式合约本身中声明的

交易执行模型

Reactive Contract 中存在两类交易:Reactive Network 交易、ReactVM 交易


Reactive Network 交易

Reactive Network 中交易有两种来源:


1 用户触发

用户可以调用反应式网络的 RC 实例的方法来执行管理功能或更新合同状态。

例如:暂停事件订阅可以通过调用pause()函数实现:

1
function pause() external rnOnly onlyOwner

执行流程:

  1. 获取订阅列表
  2. 调用service.unsubscribe()取消事件监听。

pause()函数防止RC对事件作出反应,取消订阅,从而有效阻止后续事件驱动的交易,直到事件恢复。


resume()

恢复监听:service.subscribe()

resume() 函数会重新订阅这些相同的事件,以便在新事件出现时 RC 继续响应


2 事件触发

即使用户没有发送交易,Reactive Network 也会:

  1. 监听源链事件
  2. 分发给 ReactVM
  3. ReactVM 执行逻辑

ReactVM 交易

ReactVM 中的交易, 用户无法直接调用,只能通过事件触发:

1
2
3
4
5
6
7
Origin Chain Event

Reactive Network

ReactVM

react()

react() 作用

  1. 更新状态
  2. 执行业务逻辑
  3. 发送 Callback

Callback 触发跨链交易

Callback 可以:

  • 在其他链执行交易
  • 自动触发

无需用户操作。

订阅事件(Event Subscription)

Reactive Network 的核心能力就是 **订阅事件 (Event Subscription)**。Reactive Contract 不会直接调用目标链。而是emit Callback(...),由 Reactive Network 去执行。

Reactive Contract 可以:

1
2
3
4
5
6
7
  订阅某个链上事件

当事件发生

Reactive Network 自动调用 react()

合约执行逻辑

结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
Origin Chain (如 Sepolia)

│ emit Event

Reactive Network

│ 检测匹配 subscription

Reactive Contract

│ 调用 react()

执行逻辑

所以核心流程:

  1. 订阅事件
  2. 事件发生
  3. react() 被触发
  4. 执行 callback

订阅是通过 Reactive Network System Contract 完成的:

接口:

1
2
3
4
5
6
7
8
9
10
11
12
interface ISubscriptionService {
function subscribe(
uint256 chain_id,
address _contract,
uint256 topic_0,
uint256 topic_1,
uint256 topic_2,
uint256 topic_3
) external;

function unsubscribe(...) external{};
}

常见订阅方式

Wildcard(通配符)

Reactive Network 支持通配:

含义
address(0) 任意合约
uint256(0) 任意 chain
REACTIVE_IGNORE 任意 topic

例如:topic_1 = REACTIVE_IGNORE

表示匹配任何 topic1

1 监听某个合约的所有事件

1
2
3
4
5
6
7
8
service.subscribe(
CHAIN_ID,
0xContract,
REACTIVE_IGNORE,
REACTIVE_IGNORE,
REACTIVE_IGNORE,
REACTIVE_IGNORE
);

含义:监听这个合约的所有事件


2 监听某种 Event

例如:UniswapV2 Sync 的topic0:0x1c411e9a96e07124...

1
2
3
4
5
6
7
8
service.subscribe(
CHAIN_ID,
0,
SYNC_TOPIC,
REACTIVE_IGNORE,
REACTIVE_IGNORE,
REACTIVE_IGNORE
);

含义:监听所有合约的 Sync 事件


3 同时限制 合约 + 事件

1
2
3
4
5
6
7
8
service.subscribe(
CHAIN_ID,
0xUniswapPair,
SYNC_TOPIC,
REACTIVE_IGNORE,
REACTIVE_IGNORE,
REACTIVE_IGNORE
);

含义:监听某个 pair 的 Sync

react() 函数

用于处理传入事件通知的关键函数。react()只能被 Reactive Network 调用

该函数接收一个 LogRecord 作为输入,使得响应式合约(reactive contracts)能够动态地处理事件日志。

Reactive Contract 必须实现:function react(LogRecord calldata log) external{}

当事件发生时,Reactive Network 会调用react(log)

1
2
3
4
5
6
7
8
9
10
11
function react(LogRecord calldata log) external vmOnly {
if (log.topic_0 == SUBSCRIBE_TOPIC) {
...
}
else if (log.topic_0 == UNSUBSCRIBE_TOPIC) {
...
}
else {
...
}
}

根据 topic0 判断是什么事件

动态订阅 (Dynamic Subscription)

Reactive Contract 可以根据事件动态修改 subscription

例如:

1
2
3
4
5
6
7
用户订阅

监听 approval

用户取消订阅

停止监听

实现:subscribe()unsubscribe()

构造函数订阅(Constructor Subscribtion)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// State specific to reactive network instance of the contract
address private _callback;

// State specific to ReactVM instance of the contract
uint256 public counter;

constructor(
address _service,
address _contract,
uint256 topic_0,
address callback
) payable {
service = ISystemContract(payable(_service));
if (!vm) {
service.subscribe(
CHAIN_ID,
_contract,
topic_0,
REACTIVE_IGNORE,
REACTIVE_IGNORE,
REACTIVE_IGNORE
);
}
_callback = callback;
}

Subscription 限制

Reactive Network 为了效率限制很多规则。

不支持:

至少有一个标准必须是具体的数值,以确保有意义的订阅。

  • 范围 ,例如amount > 100
  • OR ,例如 topic1 == A OR B
  • 集合 ,例如 topic in [A,B,C]

订阅不能使用 <>、区间或按位运算来匹配事件参数,只能使用严格的等值匹配。

订阅不能在单次订阅中使用“或”条件或多值集合。虽然多次调用 subscribe() 可以实现类似功能,但可能导致组合爆炸(combinatorial explosion)。


只支持

严格等于topic1 == X

禁止的订阅

由于数据量太大,以下订阅 不允许

不允许同时订阅所有链或所有合约的事件。只订阅单条链上的所有事件也不允许,因为被认为是不必要的。

  • 所有链 chain_id = 0
  • 所有合约 contract = address(0)
  • 所有 eventtopic0 = ignore

技术上允许重复订阅,但它们只作为一次订阅生效。用户每次发送交易给系统合约都要付费。由于 EVM 存储限制,在系统合约中防止重复订阅成本高昂,因此允许重复订阅以降低费用。

unsubscribe 为什么贵

取消订阅(Unsubscribing)是一项开销较大的操作,因为需要搜索并删除已有的订阅记录。允许存在重复或重叠的订阅,但客户端必须确保操作具有幂等性(idempotency)。

取消订阅需要:

  1. 查找 subscription
  2. 删除 storage

所以gas expensive

Examples

  • 订阅特定合约的所有事件

你可以订阅来自0x7E0987E5b3a30e3f2828572Bb659A548460a3003的特定合约中所有事件。

1
2
3
4
5
6
7
8
service.subscribe(
CHAIN_ID,
0x7E0987E5b3a30e3f2828572Bb659A548460a3003,
REACTIVE_IGNORE,
REACTIVE_IGNORE,
REACTIVE_IGNORE,
REACTIVE_IGNORE
);
  • 订阅特定事件主题(Uniswap V2 Sync)

你可以订阅所有Uniswap V2 Sync 事件,其 topic_0 为:
0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1

1
2
3
4
5
6
7
8
service.subscribe(
CHAIN_ID,
0, // 任意合约
0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1,
REACTIVE_IGNORE,
REACTIVE_IGNORE,
REACTIVE_IGNORE
);
  • 组合参数

你可以将参数组合,订阅特定合约的特定事件:

1
2
3
4
5
6
7
8
service.subscribe(
CHAIN_ID,
0x7E0987E5b3a30e3f2828572Bb659A548460a3003,
0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1,
REACTIVE_IGNORE,
REACTIVE_IGNORE,
REACTIVE_IGNORE
);
  • 处理来自不同来源的多个事件

要响应来自不同来源的多个事件,可以在构造函数中调用多次 subscribe

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
constructor(
address _service,
address _contract1,
address _contract2,
uint256 topic_0,
address callback
) payable {
// 初始化订阅服务
SubscriptionService service = SubscriptionService(payable(_service));

if (!vm) {
// 第一次订阅
service.subscribe(
CHAIN_ID,
_contract1,
REACTIVE_IGNORE,
REACTIVE_IGNORE,
REACTIVE_IGNORE,
REACTIVE_IGNORE
);

// 第二次订阅
service.subscribe(
CHAIN_ID,
address(0), // 任意合约
topic_0,
REACTIVE_IGNORE,
REACTIVE_IGNORE,
REACTIVE_IGNORE
);

// 可按需添加更多订阅
}

// 设置回调
_callback = callback;
}

Gas 与费用模型

Reactive Contracts 在执行 callback 时需要支付 gas。费用来源是RC的合约余额。

在 Reactive Network 中:

RVM 交易和 Callback 会先执行,执行时不会立即检查 gas price 或扣费,然后再进行费用结算。

RC合约有两种状态,可在Reactscan查看:active(合约可以正常执行)、inactive(合约存在未结清的债务,需要结算)。

Reactive Contract 在执行 RVM 交易前 必须拥有足够的 REACT 资金。如果执行之后欠费debt,合约会被标记为inactive(停用)。此时的交易也就是一次性交易。

当合约被标记为inactive后:

  • 新事件 **不会再触发 react()**;
  • callback 不会执行
  • 合约 被暂停
  • 只有执行coverDebt()补齐欠款之后合约才会变为active(活跃)。
    • 只有先向合约充值之后再调用coverDebt()函数才有效。

因此:

  • 合约必须保持 足够的余额
  • 否则合约会被 停用(inactive)

Reactive Network 的 RVM 交易是事件驱动执行,不是用户发起交易,触发者是:监听节点和网络系统。所以RC允许先执行后结算费用,而不是像普通EVM必须先付gas再执行。

流程:

1
2
3
4
5
6
7
8
9
      事件触发

Reactive Network 执行 react()

emit Callback

节点向目标链发送交易

使用 RC 余额支付 gas

如果余额不足,callback 不会执行

RVM Transactions(RVM 交易)

RVM 交易在执行时 不包含 gas price

费用会在之后进行计算,通常使用:

后续区块(通常是下一个区块)的 base fee

由于费用是在 区块级别统一结算 的,因此:

Reactscan 无法将费用精确关联到单个 RVM 交易。

==RVM交易的最大Gas Limit 为 900000 gas==

Reactive Transaction Fee(Reactive 交易费用)

Reactive Network 的交易费用计算公式:
$$
fee = BaseFee \times GasUsed
$$
其中:

BaseFee:记账区块中的 base fee,即 每单位 gas 的基础费用

GasUsed:交易执行消耗的 gas

Reactive Network 上的普通交易遵循标准 EVM gas 模型

Reactive Network 中,系统合约 和 callback proxy 使用同一个地址:0x0000000000000000000000000000000000fffFfF

可以直接向合约充值:cast send $CONTRACT_ADDR --rpc-url $REACTIVE_RPC --private-key $REACTIVE_PRIVATE_KEY --value 0.1ether

偿还债务:cast send --rpc-url $REACTIVE_RPC --private-key $REACTIVE_PRIVATE_KEY $CONTRACT_ADDR "coverDebt()"

也可以通过系统合约充值:cast send --rpc-url $REACTIVE_RPC --private-key $REACTIVE_PRIVATE_KEY $SYSTEM_CONTRACT_ADDR "depositTo(address)" $CONTRACT_ADDR --value 0.1ether

特点:发送者支付交易费用,系统会自动结清债务。

Callback Pricing(Callback 定价)

Callback 的费用取决于:

  • 目标链
  • 当前 gas 价格

Callback 价格计算公式:
$$
p_{callback} = p_{base} \cdot C \cdot (g_{callback} + K)
$$
参数解释:

  • $p_{base}$:基础 gas 价格:tx.gaspriceblock.basefee
  • C:目标网络定价系数(Destination Network Pricing Coefficient),用于调节不同链的费用。
  • $g_{callback}$:Callback 实际使用的 gas。
  • K:固定 gas 附加费用。

Callback Payment(Callback 支付)

Callback 使用 与 RVM 交易相同的支付模型

如果合约余额不足:

合约会被 blocklisted

即:

  • 无法执行交易
  • 无法执行 callback

可以向Callback合约充值,只有先向合约充值之后再调用coverDebt()函数合约才有效。

cast send $CALLBACK_ADDR --rpc-url $DESTINATION_RPC --private-key $DESTINATION_PRIVATE_KEY --value 0.1ether

cast send --rpc-url $DESTINATION_RPC --private-key $DESTINATION_PRIVATE_KEY $CALLBACK_ADDR "coverDebt()"

也可以通过callback proxy充值

cast send --rpc-url $DESTINATION_RPC --private-key $DESTINATION_PRIVATE_KEY $CALLBACK_PROXY_ADDR "depositTo(address)" $CALLBACK_ADDR --value 0.1ether

如果实现pay()或者继承AbstractPayer则可以实现 自动债务清算callback proxy在callback产生债务时会调用pay()。标准实现会:

  • 验证调用者
  • 检查余额
  • 自动结算债务

Callback Gas Limit

Reactive Network 强制规定:callback 最小 gas limit = 100,000 gas

如果 callback gas limit 低于该值,请求会被忽略。

因为该最小值用于保证:内部审计、必要计算

Callback Contract Balance

  • 查询余额:cast balance $CONTRACT_ADDR --rpc-url $DESTINATION_RPC

  • 查询债务:cast call $CALLBACK_PROXY_ADDR "debts(address)" $CONTRACT_ADDR --rpc-url $DESTINATION_RPC | cast to-dec

  • 查询储备金:cast call $CALLBACK_PROXY_ADDR "reserves(address)" $CONTRACT_ADDR --rpc-url $DESTINATION_RPC | cast to-dec


Reactive Contract Balance

  • 查询 REACT 余额:cast balance $CONTRACT_ADDR --rpc-url $REACTIVE_RPC

  • 查询债务:cast call $SYSTEM_CONTRACT_ADDR "debts(address)" $CONTRACT_ADDR --rpc-url $REACTIVE_RPC | cast to-dec

  • 查询储备金:cast call $SYSTEM_CONTRACT_ADDR "reserves(address)" $CONTRACT_ADDR --rpc-url $REACTIVE_RPC | cast to-dec

核心代码:

AbstractCallback

AbstractCallback 继承自 AbstractPayer.sol,用于提供 callback 授权机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// SPDX-License-Identifier: UNLICENSED

pragma solidity >=0.8.0;

import '../interfaces/IPayable.sol';
import './AbstractPayer.sol';

/// @title Abstract base contract for contracts receiving the Reactive Network callbacks.
abstract contract AbstractCallback is AbstractPayer {
address internal rvm_id;

constructor(address _callback_sender) {
rvm_id = msg.sender;
vendor = IPayable(payable(_callback_sender));
addAuthorizedSender(_callback_sender);
}

modifier rvmIdOnly(address _rvm_id) {
require(rvm_id == _rvm_id, 'Authorized RVM ID only');
_;
}
}
  • rvm_id :被授权的 ReactVM 标识。
  • vendor :callback proxy 地址。
  • modifier rvmIdOnly(address _rvm_id) :限制函数只能由指定的ReactVM调用。
  • 构造函数:
    • 将部署者(ReactVM)设为 rvm_id
    • 设置 callback proxy 为支付接收方
    • 将其加入授权支付发送者列表

AbstractPausableReactive

AbstractPausableReactive继承自 AbstractReactive.sol,提供可暂停的事件订阅机制

功能:

  • pause() 取消所有订阅
  • resume() 恢复订阅
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// SPDX-License-Identifier: UNLICENSED

pragma solidity >=0.8.0;

import '../interfaces/IReactive.sol';
import './AbstractReactive.sol';

/// @title Abstract base contract for pausable reactive contracts.
abstract contract AbstractPausableReactive is IReactive, AbstractReactive {
struct Subscription{
uint256 chain_id;
address _contract;
uint256 topic_0;
uint256 topic_1;
uint256 topic_2;
uint256 topic_3;
}

address internal owner;
bool internal paused;

constructor() {
owner = msg.sender;
}

/// @notice This function should return the list of subscriptions to pause/resume.
/// @return The list of subscriptions to pause/resume.
function getPausableSubscriptions() virtual internal view returns (Subscription[] memory);

modifier onlyOwner() {
require(msg.sender == owner, 'Unauthorized');
_;
}

/// @notice Pauses the reactive contract by unsubscribing from events using the criteria provided by the implementation.
function pause() external rnOnly onlyOwner {
require(!paused, 'Already paused');
Subscription[] memory subscriptions = getPausableSubscriptions();
for (uint256 ix = 0; ix != subscriptions.length; ++ix) {
service.unsubscribe(
subscriptions[ix].chain_id,
subscriptions[ix]._contract,
subscriptions[ix].topic_0,
subscriptions[ix].topic_1,
subscriptions[ix].topic_2,
subscriptions[ix].topic_3
);
}
paused = true;
}

/// @notice Resumed the reactive contract by subscribing to events using the criteria provided by the implementation.
function resume() external rnOnly onlyOwner {
require(paused, 'Not paused');
Subscription[] memory subscriptions = getPausableSubscriptions();
for (uint256 ix = 0; ix != subscriptions.length; ++ix) {
service.subscribe(
subscriptions[ix].chain_id,
subscriptions[ix]._contract,
subscriptions[ix].topic_0,
subscriptions[ix].topic_1,
subscriptions[ix].topic_2,
subscriptions[ix].topic_3
);
}
paused = false;
}
}

pause()函数逻辑

  • rnOnly:确保只有 Reactive Network 才能调用这个函数。
  • onlyOwner:限制只有 合约所有者(owner) 才能调用该函数。
  • **service.unsubscribe()**:用于取消订阅,使该合约不再监听特定事件(这些事件由 chain_idtopic_0 等参数定义)。

resume()函数逻辑

  • rnOnly:确保只有 Reactive Network 才能调用这个函数。
  • onlyOwner:限制只有 合约所有者(owner) 才能调用该函数。
  • **service.subscribe()**:用于重新订阅,使该合约继续监听特定事件(这些事件由 chain_idtopic_0 等参数定义)。

AbstractPayer

AbstractPayer提供 支付与债务结算功能

功能:

  • 授权支付发送者
  • vendor(系统/代理)债务结算
  • 合约直接充值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// SPDX-License-Identifier: UNLICENSED

pragma solidity >=0.8.0;

import '../interfaces/IPayer.sol';
import '../interfaces/IPayable.sol';

/// @title Abstract base contract for contracts needing to handle payments to the system contract or callback proxies.
abstract contract AbstractPayer is IPayer {
IPayable internal vendor;

/// @notice ACL for addresses allowed to make callbacks and/or request payment.
mapping(address => bool) senders;

constructor() {
}

/// @inheritdoc IPayer
receive() virtual external payable {
}

modifier authorizedSenderOnly() {
require(senders[msg.sender], 'Authorized sender only');
_;
}

/// @inheritdoc IPayer
function pay(uint256 amount) external authorizedSenderOnly {
_pay(payable(msg.sender), amount);
}

/// @notice Automatically cover the outstanding debt to the system contract or callback proxy, provided the contract has sufficient funds.
function coverDebt() external {
uint256 amount = vendor.debt(address(this));
_pay(payable(vendor), amount);
}

/// @notice Attempts to safely transfer the specified sum to the given address.
/// @param recipient Address of the transfer's recipient.
/// @param amount Amount to be transferred.
function _pay(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, 'Insufficient funds');
if (amount > 0) {
(bool success,) = payable(recipient).call{value: amount}(new bytes(0));
require(success, 'Transfer failed');
}
}

/// @notice Adds the given address to the ACL.
/// @param sender Sender address to add.
function addAuthorizedSender(address sender) internal {
senders[sender] = true;
}

/// @notice Removes the given address from the ACL.
/// @param sender Sender address to remove.
function removeAuthorizedSender(address sender) internal {
senders[sender] = false;
}
}

AbstractReactive

AbstractReactive继承AbstractPayerIReactive,是Reactive Contract 的基础合约

功能:

  • 接入系统合约
  • 提供订阅服务
  • 区分执行环境
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// SPDX-License-Identifier: UNLICENSED

pragma solidity >=0.8.0;

import '../interfaces/IReactive.sol';
import '../interfaces/ISystemContract.sol';
import './AbstractPayer.sol';

/// @title Abstract base contract for reactive contracts.
abstract contract AbstractReactive is IReactive, AbstractPayer {
uint256 internal constant REACTIVE_IGNORE = 0xa65f96fc951c35ead38878e0f0b7a3c744a6f5ccc1476b313353ce31712313ad;
ISystemContract internal constant SERVICE_ADDR = ISystemContract(payable(0x0000000000000000000000000000000000fffFfF));

/// @notice Indicates whether this is a ReactVM instance of the contract.
bool internal vm;

ISystemContract internal service;

constructor() {
vendor = service = SERVICE_ADDR;
addAuthorizedSender(address(SERVICE_ADDR));
detectVm();
}

modifier rnOnly() {
require(!vm, 'Reactive Network only');
_;
}

modifier vmOnly() {
require(vm, 'VM only');
_;
}

/// @notice Determines whether this copy of the contract is deployed to an RVM or the top-level Reactive Network by checking for the presence of the system contract at the predetermined address.
function detectVm() internal {
uint256 size;
// solhint-disable-next-line no-inline-assembly
assembly { size := extcodesize(0x0000000000000000000000000000000000fffFfF) }
vm = size == 0;
}
}

detectVm()

自动检测执行环境。

  • 有代码 → RN 环境
  • 无代码 → ReactVM

IPayable(支付和债务查询接口)

1
2
3
4
interface IPayable {
receive() external payable;
function debt(address _contract) external view returns (uint256);
}

IPayer(发起支付和接收资金的最小接口)

1
2
3
4
interface IPayer {
function pay(uint256 amount) external;
receive() external payable;
}

ISubscriptionService(事件订阅服务接口)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
pragma solidity >=0.8.0;

import './IPayable.sol';

interface ISubscriptionService is IPayable {
function subscribe(
uint256 chain_id,
address _contract,
uint256 topic_0,
uint256 topic_1,
uint256 topic_2,
uint256 topic_3
) external;

function unsubscribe(
uint256 chain_id,
address _contract,
uint256 topic_0,
uint256 topic_1,
uint256 topic_2,
uint256 topic_3
) external;
}
  • chain_id:一个 uint256 类型的值,表示事件来源链的 EIP-155 链 ID
  • _contract:在源链(origin chain)上触发该事件的合约地址。
  • topic_0、topic_1、topic_2、topic_3:事件日志中的 topics,类型为 uint256

IReactive (反应式合约的核心接口)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
pragma solidity >=0.8.0;

import './IPayer.sol';

interface IReactive is IPayer {
struct LogRecord {
uint256 chain_id;
address _contract;
uint256 topic_0;
uint256 topic_1;
uint256 topic_2;
uint256 topic_3;
bytes data;
uint256 block_number;
uint256 op_code;
uint256 block_hash;
uint256 tx_hash;
uint256 log_index;
}

event Callback(
uint256 indexed chain_id,
address indexed _contract,
uint64 indexed gas_limit,
bytes payload
);

function react(LogRecord calldata log) external;
}

结构化数据类型 LogRecord:用于存储事件日志(event log)的详细信息

  • chain_id:事件来源区块链的 ID。
  • _contract:触发该事件的合约地址。
  • topic_0 到 topic_3:日志中的索引主题(indexed topics)。
  • data:事件日志中的非索引数据(non-indexed data)。
  • block_number:事件发生所在区块的区块号。
  • op_code:可能表示某种操作码(operation code)。
  • block_hash、tx_hash 和 log_index:用于追踪事件来源及其上下文的附加标识信息。

Callback 事件(Callback Event): 这是一个用于通知订阅者某些特定事件发生的事件

  • chain_id:事件所属区块链的 ID。
  • _contract:触发该事件的合约地址。
  • gas_limit:为回调(callback)分配的最大 gas 限制。
  • payload:回调时附带的编码数据(encoded data)。

ISystemContract(系统合约接口)

ISystemContract 结合了 IPayable.solISubscriptionService.sol 的功能。它是用于支付和订阅管理的反应式网络系统合同接口。

1
2
3
4
5
6
7
8
pragma solidity >=0.8.0;

import './IPayable.sol';
import './ISubscriptionService.sol';

/// @title Interface for the Reactive Network's system contract.
interface ISystemContract is IPayable, ISubscriptionService {
}

react() 函数逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Methods specific to ReactVM contract instance
function react(LogRecord calldata log) external vmOnly {
if (log.topic_0 == SUBSCRIBE_TOPIC_0) {
bytes memory payload = abi.encodeWithSignature(
"subscribe(address,address)",
address(0),
address(uint160(log.topic_1))
);
emit Callback(REACTIVE_CHAIN_ID, address(this), CALLBACK_GAS_LIMIT, payload);
} else if (log.topic_0 == UNSUBSCRIBE_TOPIC_0) {
bytes memory payload = abi.encodeWithSignature(
"unsubscribe(address,address)",
address(0),
address(uint160(log.topic_1))
);
emit Callback(REACTIVE_CHAIN_ID, address(this), CALLBACK_GAS_LIMIT, payload);
}
}
}

System Contract(系统合约)

Reactive Network 的核心由三个合约组成:

System Contract

负责:

  • Reactive Contract 支付
  • 合约白名单 / 黑名单
  • Cron 定时事件

Callback Proxy

负责:

  • 执行跨链 callback
  • 管理资金 / 储备 / 债务
  • 限制回调权限
  • 计算 callback gas 成本

AbstractSubscriptionService

负责:

  • 事件订阅管理
  • 支持 chain / contract / topics 过滤
  • 支持通配符(REACTIVE_IGNORE)
  • 发出订阅更新事件

CRON 功能(定时任务)

SystemContract 通过在固定块间隔发送事件,提供了基于时间的 cron 自动化机制。反应式合约可以订阅这些事件,实现计划执行,无需轮询或外部自动化。只有只有授权 validator 可以调用cron()。每次调用 cron() 都会根据当前块号的整除性,发出一个或多个 Cron 事件。较长的间隔产生事件频率较低。

特点:

  • 无需外部 bot
  • 无需轮询
  • 完全链上自动化

事件规则:

根据区块号触发不同频率:

事件 区块间隔 时间 Topic0
Cron1 每个区块 ~7 秒 0xf02d6ea5c22a71cffe930a4523fcb4f129be6c804db50e4202fb4e0b07ccb514
Cron10 每 10 块 ~1 分钟 0x04463f7c1651e6b9774d7f85c85bb94654e3c46ca79b0c16fb16d4183307b687
Cron100 每 100 块 ~12 分钟 0xb49937fb8970e19fd46d48f7e3fb00d659deac0347f79cd7cb542f0fc1503c70
Cron1000 每 1000 块 ~2 小时 0xe20b31294d84c3661ddc8f423abb9c70310d0cf172aa2714ead78029b325e3f4
Cron10000 每 10000 块 ~28 小时 0xd214e1d84db704ed42d37f538ea9bf71e44ba28bc1cc088b2f5deca654677a56