时档搜索
槽seek是一个javascript 库,用于帮助找到存储槽 balanceOf 财务报告和审定财务报表 allowance 一份ERC20令牌合同中的绘图,以及许可证2允许绘图。 它还提供了一种生成模拟数据的方法,可以用来推翻合同状态 eth_call 或者说 eth_estimateGas 电话.
这个库的主要用途是估算如果地址没有所需的余额或批准,将失败的交易的天然气成本.
例如,在用户批准使用其代币的合同之前,估计交易在互换时将消耗的气体.
特征
- 查找存储槽
balanceOf财务报告和审定财务报表allowanceeRC20令牌合同中的绘图和许可证2 - 生成可用于推翻合同状态的模拟数据
eth_call编号 :eth_estimateGas调用 - 支助 vyper 存储布局
如何运作
库使用蛮横的强力方法寻找存储槽 balanceOf 财务报告和审定财务报表 allowance 一份ERC20令牌合约中的绘图. 它通过使用一个用户提供的地址来做到这一点,我们知道该地址有平衡或批准,然后通过合同的存储槽通过 eth_getStorageAt JSON-RPC方法直到找到存储值与用户的余额或批准相匹配的槽.
这不是一个完美的方法,除了直接与RPC的合同互动外,还有更有效的方法可以找到存储槽. 但若不需要设置更多的工具/基本工具,特别是多链支持和运行时的气体估计,则很难做到这一点. 而且,在Javascript中帮助解决这个问题的工具也不多.
安装
npm install @d3or/slotseek
# or
yarn add @d3or/slotseek
可选校验出缓存
应用程序可以提供任何Aync缓存执行. 槽seek 不创建 网络连接或依赖于特定的缓存客户端.
import { generateMockBalanceData, StorageLayoutCacheAdapter } from "@d3or/slotseek";
const cache: StorageLayoutCacheAdapter = {
get: async (key) => JSON.parse((await redis.get(key)) ?? "null"),
set: async (key, layout, ttlSeconds) => {
await redis.set(key, JSON.stringify(layout), "EX", ttlSeconds);
},
};
await generateMockBalanceData(provider, {
tokenAddress,
holderAddress,
mockAddress,
cache,
chainId: 8453, // optional; otherwise resolved from provider.getNetwork()
cacheTtlSeconds: 7 * 24 * 60 * 60,
cacheTimeoutMs: 200,
});
密钥通过布局类型,链式ID,和小写代号来进行版本和范围化.
根据正链上余额或备抵核实的布局被缓存
长 cacheTtlSeconds TTL( 默认7天) 。 格式错误、 过期、 失败或
缓存读取的缓存被视作缺失, 缓存写入失败不会失败槽
发现.
负缓冲
失败发现也被缓存, TTL 较短
(单位:千美元)negativeCacheTtlSeconds,默认为15分钟),所以重复引用
不支持的符号不会在每次呼叫时重运行完整的存储探测器 :
- 发现零备抵(理由)
zero-allowance)或(或) 用尽所有探测器(理由)not-found)写作负标记. 标记 键入(kind, chainId, token),但津贴为所有者/发放者 具体,那么新鲜zero-allowance击出以 a 为单位的当前配对 单人allowance()电话:如果仍然是零,呼叫者收到 倒置批准槽(10),没有储存探测器;如果是正的 标记被忽略了 完全发现这对. - 平衡的发现,所有探测器都耗尽了 尽管在链上呈正值
余额写入 a
not-found标记,没有RPC,新鲜的点击会很快失败. 零平衡的结果是持有者特有的,从不负的. not-found标记记录探测预算(maxSlots已使用;一个呼叫者 搜索比所覆盖的标记更多的插槽会忽略它并重试.- 拒绝的存储探测器( 速率限制、 提供者错误) 的搜索 视之为瞬息万变 绝不是阴性.
- 负标记带有绝对值
expiresAt作者设置; 读者 尊重最低程度和他们自己的negativeCacheTtlSeconds,所以一个 标记永远不会超过它的作家TTL。 期满后,发现再次运行. - 同一发现的同名呼叫者(同同名信使和同名信使) 持有者/所有人/喷雾器和探测器预算)分享飞行中的结果,包括: 失败和拒绝,所以爆发 相同的引用最多一个 探测器序列.
观察性
过 onCacheEvent 以接收结构化缓存遥测以连接到
指标或日志。 活动 type, (中文(简体) ), kind (单位:千美元)balance 编号 : approval, (中文(简体) ),
chainId, (中文(简体) ), tokenAddress,和(对于负面结果) reason。事件类型 :
local_hit- 从程序内缓存得到服务external_hit- 从应用程序提供的缓存适配器服务negative_hit- 一个新的负标记被消耗verified- 发现成功,布局被缓存discovery_failed- 发现失败,结果为负面cache_error- 外部适配器抛出或超时(无法打开)
回调可以是同步或同步;例外和拒绝被吞噬.
await generateMockApprovalData(provider, {
...args,
cache,
onCacheEvent: (event) => metrics.increment(`slotseek.cache.${event.type}`),
});
开始
- 添加缓存选项以减少 RPC 调用的次数并减少再次找到相同槽所需的时间
用户通过 eth call 实现平衡的示例
import { ethers } from "ethers";
import { generateMockBalanceData } from "@d3or/slotseek";
async function fakeUserBalance() {
// Setup - Base RPC
const provider = new ethers.providers.JsonRpcProvider("YOUR_RPC_URL");
// Constants
const tokenAddress = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"; // USDC on Base
const holderAddress = "0x0000c3Caa36E2d9A8CD5269C976eDe05018f0000"; // USDC holder
const mockAddress = ethers.Wallet.createRandom().address; // Address to fake balance for
const mockBalanceAmount = "1000000000000"; // 1 million USDC (6 decimal places), optional. If not provided, defaults to the balance of the holder
// Generate mock balance data
const data = await generateMockBalanceData(provider, {
tokenAddress,
holderAddress,
mockAddress,
mockBalanceAmount,
});
// Prepare state diff object
const stateDiff = {
[tokenAddress]: {
stateDiff: {
[data.slot]: data.balance,
},
},
};
// Prepare balanceOf call
const balanceOfSelector = "0x70a08231";
const encodedAddress = ethers.utils.defaultAbiCoder
.encode(["address"], [mockAddress])
.slice(2);
const getBalanceCalldata = balanceOfSelector + encodedAddress;
// Make the eth_call with state overrides, or eth_estimateGas
const balanceOfResponse = await provider.send("eth_call", [
{
from: mockAddress,
to: tokenAddress,
data: getBalanceCalldata,
},
"latest",
stateDiff,
]);
// Decode and log the result
const balance = ethers.BigNumber.from(
ethers.utils.defaultAbiCoder.decode(["uint256"], balanceOfResponse)[0]
);
console.log(
`Mocked balance for ${mockAddress}: ${ethers.utils.formatUnits(
balance,
6
)} USDC`
);
}
fakeUserBalance().catch(console.error);
这也可以用来伪造批准,使用 generateMockApprovalData 函数代替 generateMockBalanceData。 。 。 .
import { ethers } from "ethers";
import { generateMockApprovalData } from "@d3or/slotseek";
async function fakeUserApproval() {
// Setup
const provider = new ethers.providers.JsonRpcProvider("YOUR_RPC_URL");
// Constants
const tokenAddress = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"; // USDC on Base
const ownerAddress = "0x0000c3Caa36E2d9A8CD5269C976eDe05018f0000"; // USDC holder
const spenderAddress = "0x000000000022D473030F116dDEE9F6B43aC78BA3"; // Spender address
const mockAddress = ethers.Wallet.createRandom().address; // Address to fake balance for
const mockApprovalAmount = "1000000000000"; // 1 million USDC (6 decimal places)
// Generate mock approval data
const mockApprovalData = await generateMockApprovalData(provider, {
tokenAddress,
ownerAddress,
spenderAddress,
mockAddress,
mockApprovalAmount,
});
// Prepare state diff object
const stateDiff = {
[tokenAddress]: {
stateDiff: {
[mockApprovalData.slot]: mockApprovalData.approval,
},
},
};
// Function selector for allowance(address,address)
const allowanceSelector = "0xdd62ed3e";
// Encode the owner and spender addresses
const encodedAddresses = ethers.utils.defaultAbiCoder
.encode(["address", "address"], [mockAddress, spenderAddress])
.slice(2);
const getAllowanceCalldata = allowanceSelector + encodedAddresses;
// Make the eth_call with state overrides, or eth_estimateGas
const allowanceResponse = await provider.send("eth_call", [
{
from: mockAddress,
to: tokenAddress,
data: getAllowanceCalldata,
},
"latest",
stateDiff,
]);
// Decode and log the result
const allowance = ethers.BigNumber.from(
ethers.utils.defaultAbiCoder.decode(["uint256"], allowanceResponse)[0]
);
console.log(
`Mocked allowance for ${mockAddress}: ${ethers.utils.formatUnits(
allowance,
6
)} USDC`
);
}
fakeUserApproval().catch(console.error);
您也可以同时通过提供 balance 财务报告和审定财务报表 approval 状态 diff 对象中的字段.
在合同中找到存储槽的例子
import { ethers } from "ethers";
import { getErc20BalanceStorageSlot } from "@d3or/slotseek";
async function findStorageSlot() {
// Setup - Base RPC
const provider = new ethers.providers.JsonRpcProvider(
"https://mainnet.base.org"
);
// Constants
const tokenAddress = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"; // USDC on Base
const holderAddress = "0x0000c3Caa36E2d9A8CD5269C976eDe05018f0000"; // USDC holder
const maxSlots = 100; // Max slots to search
// Find the storage slot for the balance of the holde
// or for approvals, use getErc20AllowanceStorageSlot
const { slot, balance, isVyper } = await getErc20BalanceStorageSlot(
provider,
tokenAddress,
holderAddress,
maxSlots
);
console.log(
`User has balance of ${ethers.utils.formatUnits(
balance,
6
)} USDC stored at slot #${Number(slot)}`
);
}
findStorageSlot().catch(console.error);
嘲弄许可证的图示2
import { ethers } from "ethers";
import { computePermit2AllowanceStorageSlot } from "@d3or/slotseek";
async function findStorageSlot() {
// Setup - Base RPC
const provider = new ethers.providers.JsonRpcProvider(
"https://mainnet.base.org"
);
// Constants
const tokenAddress = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"; // USDC on Base
const mockAddress = "0x0000c3Caa36E2d9A8CD5269C976eDe05018f0000"; // USDC holder to mock approval for
const spenderAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
// Compute storage slot of where the allowance would be held
const { slot } = computePermit2AllowanceStorageSlot(mockAddress, tokenAddress, spenderAddress)
const permit2Contract = '0x000000000022d473030f116ddee9f6b43ac78ba3'
// Prepare state diff object
const stateDiff = {
[permit2Contract]: {
stateDiff: {
[slot]: ethers.utils.hexZeroPad(
ethers.utils.hexlify(ethers.BigNumber.from("1461501637330902918203684832716283019655932142975")),
32
)
,
},
},
};
// Function selector for allowance(address,address,address)
const allowanceSelector = "0x927da105";
// Encode the owner and spender addresses
const encodedAddresses = ethers.utils.defaultAbiCoder
.encode(["address", "address", "address"], [mockAddress, tokenAddress, spenderAddress])
.slice(2);
const getAllowanceCalldata = allowanceSelector + encodedAddresses;
const callParams = [
{
to: permit2Contract,
data: getAllowanceCalldata,
},
"latest",
];
const allowanceResponse = await baseProvider.send("eth_call", [
...callParams,
stateDiff,
]);
// convert the response to a BigNumber
const approvalAmount = ethers.BigNumber.from(
ethers.utils.defaultAbiCoder.decode(["uint256"], allowanceResponse)[0]
);
console.log(
`Mocked balance for ${mockAddress}: ${ethers.utils.formatUnits(
approvalAmount,
6
)} USDC`
);
}
findStorageSlot().catch(console.error);