本文以事故为核心,聚焦 Redstone Oracles 的技术细节、失败原因、解决方案及预防措施,结构清晰,注重知识点讲解。
在 Scroll 网络(Layer 2,链 ID:534352)上,我尝试通过疑似 Loanshark 的借贷协议(loanshark)赎回抵押的 0.023162 ETH,但调用 CETH 合约(地址:0xF017f9CF11558d143E603d56Ec81E4E3B6d39D7F)的 redeem 和 redeemUnderlying 方法失败。通过 Tenderly 工具分析,错误源于 Redstone Oracles 的 _extractByteSizeOfUnsignedMetadata 函数,提示交易 calldata 缺少或包含无效的 Redstone payload。最终,我通过 JavaScript 脚本结合 Redstone 的 evm-connector 库成功赎回资产。
本文以事故为核心,聚焦 Redstone Oracles 的技术细节、失败原因、解决方案及预防措施,结构清晰,注重知识点讲解。
我在 Scroll 网络上通过借贷协议抵押了 0.023162 ETH,试图赎回时,通过 Scrollscan 调用 CETH 合约的 redeem 和 redeemUnderlying 方法失败。Tenderly 分析显示错误发生在 Redstone Oracles 的 _extractByteSizeOfUnsignedMetadata 函数,提示 calldata 缺少有效 Redstone payload。最终,我通过脚本使用 Redstone 的 evm-connector 库成功赎回。
相关合约:
0xF017f9CF11558d143E603d56Ec81E4E3B6d39D7F0xEFB0697700E5c489073a9BDF7EF94a2B2bc884a5https://api.redstone.finance/prices?symbol=ETH&provider=redstone-rapid&limit=1)报错。redeem 和 redeemUnderlying,均失败。_extractByteSizeOfUnsignedMetadata。Redstone Oracles 是一个去中心化预言机系统,用于将链下数据(如价格)注入智能合约。它通过在交易 calldata 末尾附加 payload 传递数据,适用于 DeFi 协议(如借贷、DEX),支持价格喂价、链下计算等。
Redstone payload 是附加在 calldata 末尾的数据包,包含:
0x000002ed57011e0000),位于末尾。keccak256("ETH/USD")。示例:
[...交易数据...][数据包][元数据][大小: 3字节][marker: 9字节]
数据包(ETH/USD):
[签名: 65字节][数据点数: 1][值大小: 32][ETH/USD ID][价格: 3000e8][时间戳]
_extractByteSizeOfUnsignedMetadata 函数详解function _extractByteSizeOfUnsignedMetadata() internal pure returns (uint256) {
bool hasValidRedstoneMarker;
assembly {
let calldataLast32Bytes := calldataload(sub(calldatasize(), 32))
hasValidRedstoneMarker := eq(REDSTONE_MARKER_MASK, and(calldataLast32Bytes, REDSTONE_MARKER_MASK))
}
if (!hasValidRedstoneMarker) revert CalldataMustHaveValidPayload();
uint24 unsignedMetadataByteSize;
if (41 > msg.data.length) revert CalldataOverOrUnderFlow();
assembly {
unsignedMetadataByteSize := calldataload(sub(calldatasize(), 41))
}
uint256 calldataNegativeOffset = unsignedMetadataByteSize + 3 + 9;
if (calldataNegativeOffset + 2 > msg.data.length) revert IncorrectUnsignedMetadataSize();
return calldataNegativeOffset;
}元数据大小 + 3 + 9。CalldataMustHaveValidPayload:无 marker。CalldataOverOrUnderFlow:calldata 过短。IncorrectUnsignedMetadataSize:偏移无效。Comptroller 的 redeemAllowed 函数依赖 Redstone 价格数据(如 ETH/USD),用于:
redeem 或 redeemUnderlying。Comptroller.redeemAllowed,通过 ProxyConnector 转发 calldata。ProxyConnector 使用 CalldataExtractor 解析 payload,提取价格。redeem 时,未附加 Redstone payload,_extractByteSizeOfUnsignedMetadata 无法找到 marker,抛出 CalldataMustHaveValidPayload。Comptroller 无法获取价格数据,赎回验证失败。SignerNotAuthorised。DataPackageTimestampMustNotBeZero 或 DataPackageTimestampsMustBeEqual。https://api.redstone.finance/prices)暗示配置问题。evm-connector 生成包含 ETH/USD 价格的 payload。https://rpc.scroll.io,链 ID:534352)。以下是成功赎回的脚本:
require('dotenv').config();
const ethers = require('ethers'); // 5.7.2
const { WrapperBuilder } = require('@redstone-finance/evm-connector'); // 0.7.3
const scrollRpcUrl = 'https://rpc.scroll.io';
const cethAddress = '0xF017f9CF11558d143E603d56Ec81E4E3B6d39D7F';
const cethAbi = ['function redeemUnderlying(uint redeemAmount) external returns (uint)'];
const redstoneConfig = {
dataServiceId: 'redstone-primary-prod',
uniqueSignersCount: 1,
dataPackagesIds: ['ETH']
};
async function executeRedeem() {
const provider = new ethers.providers.JsonRpcProvider(scrollRpcUrl);
const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
const cethContract = new ethers.Contract(cethAddress, cethAbi, signer);
const wrappedCethContract = WrapperBuilder.wrap(cethContract).usingDataService(redstoneConfig);
const tx = await wrappedCethContract.redeemUnderlying(ethers.utils.parseUnits('1', 18), { gasLimit: 1000000 });
const receipt = await tx.wait();
console.log('赎回成功! 交易哈希:', receipt.transactionHash);
}
executeRedeem().catch(console.error);
关键点:
ethers@5.7.2 和 evm-connector@0.7.3 确保兼容性。Comptroller 需求。Comptroller 的 redeemAllowed 需 Redstone 价格数据验证赎回。evm-connector 自动附加 payload,避免手动调用。ETH)和数据服务(redstone-primary-prod)正确,参考 Redstone 文档.事故原因:在 Scrollscan 手动调用 redeem 时,缺少 Redstone payload,导致 _extractByteSizeOfUnsignedMetadata 抛出错误。Comptroller 依赖价格数据验证赎回,无 payload 导致失败。
解决方案:使用 evm-connector 脚本生成 payload,成功调用 redeemUnderlying,赎回 ETH。
经验教训:
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!