EVM中的事件
事件允许智能合约在满足特定条件时,通过记录特定信息来与外部世界进行通信。有了事件,去中心化应用(dApps)就可以直接响应发生的动作,而不需要不停地主动查询(轮询)区块链状态。
EVM 会对事件进行索引(Index),这使得监控区块链活动(如转账、价格变化)变得非常高效和容易。当合约触发事件时,数据被存储在交易的日志(Logs)中。这些日志虽然附着在区块链的区块上,但它们不会直接影响区块链的状态(例如,它们不会改变账户余额或合约变量的值)。
开发者通过两个核心关键字来使用事件:
触发(Emit):使用 emit 关键字来正式记录数据。
例如:emit PriceUpdated("ETH", newEthPrice);定义(Define):使用 event 关键字,后接事件名称和要记录的数据类型。
例如:event PriceUpdated(string symbol, uint256 newPrice);设想一个需要实时价格信息来执行其逻辑的智能合约,例如一个根据最新市场价格动态调整抵押品要求的去中心化金融(DeFi)借贷平台。该合约可能定义如下事件:
event PriceUpdated(string symbol, uint256 newPrice);
当智能合约从 Chainlink 的预言机接收到新的价格更新时,会触发 PriceUpdated 事件:
emit PriceUpdated("ETH", newEthPrice);
响应式合约中的事件处理
接口
为了实现自动化反应,响应式合约必须遵循一套标准的交互协议,即实现 IReactive 接口。
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 结构
当源链(如以太坊)发生事件时,睿应网络会将该事件打包成一个 LogRecord 结构体发送给合约。你可以把它想象成一封包含所有关键信息的“挂号信”:
具体来说:
chain_id:事件来源的区块链 ID。_contract:发出该事件的合约地址。topic_0至topic_3:日志的已索引主题(indexed topics)。data:事件日志中的未索引数据。block_number:事件发生的区块编号。op_code:可能表示操作码。block_hash、tx_hash和log_index:用于追踪事件来源与上下文的其他标识符。
react()是睿应合约的核心入口。
- 触发机制:响应式网络会持续监控日志,一旦发现符合你设置的订阅条件的事件,就会自动调用这个
react()方法,并将上述LogRecord丢进去。 - 动态处理:通过解析输入的数据,合约可以动态决定接下来要做什么(比如:如果价格低于 X,就准备卖出)。
- 权限限制:它被标记为
external,意味着它只接受来自网络层的主动触发,而不能被合约内部随意调用。
Callback回调
响应式合约本身运行在睿应网络中,它不能直接在以太坊上写数据。因此,它通过发出一个特定格式的日志(即 Callback 事件),告诉睿应网络:“请帮我在那条链上执行这个操作”。
因此当 react() 函数决定要行动时,它不会直接去操作目标链,而是通过抛出一个 Callback 事件来发出指令。
- 这个事件包含了目标链的 ID、目标合约地址、Gas 限制以及最重要的 payload(执行载荷)。
- 睿应网络捕捉到这个
Callback后,会负责在目标链上把这笔交易“跑”出来。
具体来说:
chain_id:该事件的区块链 ID。_contract:触发该事件的合约地址。gas_limit:为回调函数分配的最大 Gas 量。payload:随回调函数一同传递的编码数据。
整个过程是完全自动化且去中心化的:
- 触发(Emit):当响应式合约的逻辑判断需要采取行动时,它会
emit Callback(...)。 - 检测(Detect):睿应网络(Reactive Network)作为一个底层基础设施,会实时监控这些回调事件。
- 处理与提交(Process & Submit):一旦检测到事件,网络会解析
payload中的交易详情,并使用你提供的chain_id和gas_limit,在目标链上创建并提交一笔真实的交易。
需要注意的是,代码并不是直接跑在主网上,而是在一个名为 ReactVM 的私有沙箱中运行,运行后的callback才有睿应网执行跨链操作。
- 隔离性:响应式合约只能与同一个部署者部署的合约进行交互。
- 安全性:这种隔离机制确保了即使在处理复杂的跨链事件时,合约环境也是受控且安全的,防止了恶意的跨合约攻击。
身份替换机制
在传统的跨链操作中,证明“谁发起了请求”通常非常复杂。睿应层通过协议层的强制操作简化了这一过程:
- 强制覆盖:当你构建
payload(执行载荷)时,无论你为第一个参数填入了什么值,响应式网络在将交易提交到目标链之前,都会自动且强制性地将前 160 位(即第一个参数的位置)替换为调用方合约的 RVM ID。 - RVM ID 的本质:这个 ID 等同于该响应式合约在 ReactVM 中的地址,且与该合约部署者的地址完全一致。
- 不可伪造性:由于替换发生在网络底层,开发者无法通过代码篡改这个身份标识。这为目标链合约提供了一个不可信环境下的“信任原点”。
这一机制直接影响了你编写 Solidity 代码的方式:
- 参数类型限制:你目标链上的被调用函数,其第一个参数必须是
address类型。 - 变量名无关性:无论你在目标链合约中给第一个参数起什么名字(比如
caller或origin),它接收到的永远是发起请求的 RC 合约地址。
一个Uniswap 止损单例子:
bytes memory payload = abi.encodeWithSignature(
"stop(address,address,address,bool,uint256,uint256)",
address(0), // The ReactVM address
pair, // The Uniswap pair address involved in the transaction
client, // The address of the client initiating the stop order
token0, // The address of the first token in the pair
coefficient, // A coefficient determining the sale price
threshold // The price threshold at which the sale should occur
);
emit Callback(chain_id, stop_order, CALLBACK_GAS_LIMIT, payload);
这里需要注意的是第一个参数填 address(0),起到占位符作用。