ERC-7412: 按需链下数据检索
一种构建多重调用的方法,该方法预先添加了可验证的链下数据
Authors | Noah Litvin (@noahlitvin), db (@dbeal-eth) |
---|---|
Created | 2023-07-26 |
Discussion Link | https://ethereum-magicians.org/t/erc-7412-on-demand-off-chain-data-retrieval/15346 |
摘要
合约可能需要在执行期间使用链下数据。智能合约函数可以通过返回 error OracleDataRequired(address oracleContract, bytes oracleQuery, uint256 feeRequired)
来实现此处提出的标准。支持此标准的客户端将在请求的模拟期间识别此错误消息,向指定的去中心化预言机网络查询签名数据,并转而使用预先添加了所需链下数据验证的多重调用来暂存交易。数据将在验证期间写入链上的智能合约,供后续调用读取,从而避免错误。
动机
以太坊的扩容路线图涉及一系列独立的智能合约代码执行环境(包括二层和三层扩容解决方案)。这使得跨多个链读取数据的能力对于构建可扩展的应用程序至关重要。此外,对于依赖价格数据的去中心化金融协议来说,期望预言机网络能够持续地将新鲜数据推送到每个二层和三层网络以支持任意数量的价格源是不合理的。
跨链桥正在开发中,智能合约函数可以将数据写入其他链。需要一个类似的標準,使能够从其他链读取数据。该标准可以推广到从去中心化预言机网络读取任何链下数据,包括价格源。
通过写入和读取跨链数据的标准,协议开发人员将能够为异步性创建抽象(这是在其他软件工程环境中被彻底探讨的主题)。这将能够开发出不受扩展约束的高度复杂的协议。
ERC-3668 引入了使用 reverts 来要求链下数据,但是该标准的具体细节引入了各种挑战,这些挑战在下面的 Rationale 部分中概述。通过利用多重调用而不是回调函数,此处提出的标准能够克服其中的一些约束。
规范
任何实现此标准的合约在需要链下数据时必须返回以下错误:
error OracleDataRequired(address oracleContract, bytes oracleQuery, uint256 feeRequired)
oracleQuery
指定了所需的链下数据。此参数的有效数据格式特定于预言机合约指定的预言机 ID。这可能包括链 ID、合约地址、函数签名、有效负载和时间戳/“最新”,用于跨链读取。对于价格源,它可以包括股票代码和时间戳/“最新”。
oracleContract
是可以验证链下数据并将其提供给合约以避免 OracleDataRequired
错误的合约地址。此合约必须实现以下接口:
interface IERC7412 {
function oracleId() view external returns (bytes32 oracleId);
function fulfillOracleQuery(bytes signedOffchainData) payable external;
}
oracleId
是一个唯一的标识符,它引用生成所需的签名链下数据的去中心化预言机网络。预言机 ID 类似于以太坊生态系统中的链 ID。客户端应解析与预言机 ID 对应的网关,类似于客户端应基于链 ID 解析 RPC 端点的方式。
应该可以从 signedOffchainData
推导oracleQuery
,这样预言机合约能够基于 oracleQuery
提供经过验证的链下数据。
如果实现 IERC7412
接口的合约需要支付费用才能满足预言机数据查询,则必须返回以下错误消息:
error FeeRequired(uint amount)
amount
指定执行 fulfillOracleQuery
函数所需的本地 gas 代币数量,以 wei 为单位。如果调用者提供足够的 msg.value
,以便预言机合约可以收取费用金额,则必须解决此错误。实际上,我们希望费用金额保持相对稳定(如果不是恒定的话)。
此外,为了优化需要多个预言机数据请求的场景(例如需要同时使用多个价格源的协议),该接口可以包括用于批量处理多个错误的处理逻辑:
error Errors(bytes[] errors);
这使客户端能够有效地处理单个交易模拟中的多个 OracleDataRequired
错误,从而减少所需的单独请求的数量。当遇到此错误时,客户端应解析数组中的每个错误并相应地处理它们,并可能对嵌套的错误对象使用递归。
客户端有责任决定如何构造多重调用,其中在原子交易中,fulfillOracleQuery
函数在预期的函数调用之前被调用(如果需要)。支持帐户抽象的钱包(根据 ERC-4337)应该已经具有生成原子多操作的能力。为了支持 EOA,协议可以实现 ERC-2771。标准的多重调用合约只能用于构造包含不引用 msg.sender
或 msg.data
的函数的多重调用。
请注意,可以将 URI
用作 oracleId
,并将 URI 指定为 oracleQuery
。这将允许此标准符合任意链上 URI,而无需更新客户端库,类似于 ERC-3668。
理由
该提案本质上是 ERC-3668 的替代方案,但有几个值得注意的区别:
- 错误的构造非常简单。实施此标准的开发人员只需要了解他们选择依赖的预言机网络、该网络接受的查询形式以及他们希望从中检索数据的合约。
- 通过依靠多重调用而不是回调,可以更简单地处理嵌套调用需要不同链下数据的情况。按照此处提出的标准,最终用户(包括使用实现帐户抽象的客户端的用户)始终只需要签署一个交易,而无需考虑正在执行的调用的内部结构的复杂性。客户端可以自动将任何必要的链下数据添加到交易中,以使调用成功。
通过此标准,预言机提供商不仅可以可扩展地支持无限数量的网络,而且还可以与本地/分支网络兼容以进行协议开发。
此标准的另一个主要优点是,预言机可以在链上数据验证期间以本地 gas 代币的形式收取费用。这创建了一种经济激励,可以从数据消费者那里收取费用,并将其提供给去中心化预言机网络中的节点运营商。
参考实现
以下伪代码说明了客户端 SDK 的过度简化版本。理想情况下,这可以在钱包中实现,但也可以构建到应用程序层中。此函数获取所需的交易,并将其转换为预先添加了所需的数据验证交易的多重调用,从而避免 OracleDataRequired
错误:
function prepareTransaction(originalTx) {
let multicallTx = [originalTx];
while (true) {
try {
const simulationResult = simulateTx(multicallTx);
return multicallTx;
} catch (error) {
if (error instanceof OracleDataRequired) {
const signedRequiredData = fetchOffchainData(
error.oracleContract,
error.oracleQuery
);
const dataVerificationTx = generateDataVerificationTx(
error.oracleContract,
signedRequiredData
);
multicallTx.unshift(dataVerificationTx);
}
}
}
}
预言机提供商可以创建一个合约(也可能执行一些预处理),该合约将自动触发链下数据的请求,如下所示:
contract OracleContract is IERC7412 {
address public constant VERIFIER_CONTRACT = 0x0000;
uint public constant STALENESS_TOLERANCE = 86400; // One day
mapping(bytes32 => bytes) public latestVerifiedData;
function oracleId() external pure returns (bytes32){
return bytes32(abi.encodePacked("MY_ORACLE_ID"));
}
function fulfillOracleQuery(bytes calldata signedOffchainData) payable external {
bytes memory oracleQuery = _verify(signedOffchainData);
latestVerifiedData[keccak256(oracleQuery)] = signedOffchainData;
}
function retrieveCrossChainData(uint chainId, address contractAddress, bytes payload) internal returns (bytes) {
bytes memory oracleQuery = abi.encode(chainId, contractAddress, payload);
(uint timestamp, bytes response) = abi.decode(latestVerifiedData[oracleQuery], (uint, bytes));
if(timestamp < block.timestamp - STALENESS_TOLERANCE){
revert OracleDataRequired(address(this), oracleQuery, 0);
}
return response;
}
function _verify(bytes memory signedOffchainData) payable internal returns (bytes oracleQuery) {
// 在这里插入验证代码
// 这可能会还原并显示错误 FeeRequired(uint amount)
}
}
现在,顶级协议智能合约可以实现如下跨链函数:
interface ICrosschainContract {
function functionA(uint x) external returns (uint y);
function functionB(uint x) external returns (uint y);
}
contract CrosschainAdder {
IERC7412 oracleContract = 0x0000;
function add(uint chainIdA, address contractAddressA, uint chainIdB, address contractAddressB) external returns (uint sum){
sum = abi.decode(oracleContract.retrieveCrossChainData(chainIdA, contractAddressA, abi.encodeWithSelector(ICrosschainContract.functionA.selector,1)), (uint)) + abi.decode(oracleContract.retrieveCrossChainData(chainIdB, contractAddressB, abi.encodeWithSelector(ICrosschainContract.functionB.selector,2)),(uint));
}
}
请注意,CrosschainAdder
函数的开发人员无需关心此标准的实施。add
函数可以简单地调用预言机合约上的函数,就像它正常检索链上数据一样。
像这样的跨链函数也可以用来避免链上 O(n)(及更高)的循环。例如,chainIdA
和 chainIdB
可以引用与部署 CrosschainAdder
合约的同一链,并将 functionA
和 functionB
作为具有计算密集型循环的 view 函数。
安全考虑
此标准引入的一个潜在风险是,它对多调用的依赖可能会模糊钱包应用程序中不具有更复杂的交易解码功能的交易数据。这是钱包应用程序开发人员正在解决的现有挑战,因为在协议开发中,多重调用越来越普遍,而不仅仅是在此标准之外。
请注意,验证者合约有责任确认从预言机网络提供的数据的有效性。此标准不会为将无效数据提供给智能合约创造任何新的机会。
版权
版权及相关权利通过 CC0 放弃。
Citation
Please cite this document as:
Noah Litvin (@noahlitvin), db (@dbeal-eth), "ERC-7412: 按需链下数据检索 [DRAFT]," Ethereum Improvement Proposals, no. 7412, July 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7412.