UNPKG

ethers-override

Version:
118 lines (117 loc) 5.39 kB
import { Interface, JsonRpcProvider, keccak256, solidityPacked, toBeHex, zeroPadValue } from "ethers"; import { AddressAsIf } from "./addressAsIf.js"; import erc20Abis from "./abi/erc20.json" with { type: "json" }; const erc20Interface = new Interface(erc20Abis.abi); const findSlotKeyInternal = async (toAddress, data, provider) => { const params = [{ to: toAddress, gas: "0x4c4b40", gasPrice: "0x9184e72a000", value: "0x0", data, }]; const res = await provider.send("eth_createAccessList", params); // console.log(JSON.stringify(res, null, 2)); const access = res.accessList.find(a => a.address.toLowerCase() === toAddress.toLowerCase()); if (access === undefined || access.storageKeys === undefined) { throw new Error(`Failed to get slot keys for address ${toAddress} with calldata: ${data}.`); } return access.storageKeys.filter(key => !key.startsWith("0x000000000000000000000000")).map(key => key.toLowerCase()); }; const getBalanceOfSlotKey = (ownerAddress, mappingSlotNumber) => { return keccak256(zeroPadValue(ownerAddress, 32) + toBeHex(mappingSlotNumber, 32).slice(2)).toLowerCase(); }; const balanceOfSlotNumberByToken = new Map(); const getAllowanceSlotKey = (ownerAddress, spenderAddress, mappingSlotNumber) => { return keccak256(zeroPadValue(spenderAddress, 32) + keccak256(zeroPadValue(ownerAddress, 32) + toBeHex(mappingSlotNumber, 32).slice(2)).slice(2)); }; const allowanceSlotNumberByToken = new Map(); export class TokenAsIf extends AddressAsIf { constructor(address) { super(address); this._method = undefined; this._provider = undefined; } connect(provider) { this._provider = provider; return this; } balanceOf(ownerAddress) { this._method = "balanceOf"; const mapSlotNumber = balanceOfSlotNumberByToken.get(this.address); if (mapSlotNumber === undefined) { const calldata = erc20Interface.encodeFunctionData("balanceOf", [ownerAddress]); if (this._provider === undefined) { throw new Error(`Could not detect balanceOf mapping slot number as no provider is connected to TokenAsIf ${this.address}.`); } const promise = findSlotKeyInternal(this.address, calldata, this._provider) .then(visitedSlotKeys => { for (let mappingSlotNumber = 0; mappingSlotNumber < 20; mappingSlotNumber++) { const key = getBalanceOfSlotKey(ownerAddress, mappingSlotNumber); const slotKey = visitedSlotKeys.find(vk => vk === key); if (slotKey !== undefined) { // console.log(`slotNumber: ${mappingSlotNumber}`); balanceOfSlotNumberByToken.set(this.address, mappingSlotNumber); super.stateDiff(slotKey); break; } } }); this._registeredPromises.push(promise); } else { super.stateDiff(getBalanceOfSlotKey(ownerAddress, mapSlotNumber)); } return this; } allowance(ownerAddress, spenderAddress) { this._method = "allowance"; const mapSlotNumber = allowanceSlotNumberByToken.get(this.address); if (mapSlotNumber === undefined) { const calldata = erc20Interface.encodeFunctionData("allowance", [ownerAddress, spenderAddress]); if (this._provider === undefined) { throw new Error(`Could not detect allowance mapping slot number as no provider is connected to TokenAsIf ${this.address}.`); } const promise = findSlotKeyInternal(this.address, calldata, this._provider) .then(visitedSlotKeys => { for (let mappingSlotNumber = 0; mappingSlotNumber < 20; mappingSlotNumber++) { const key = getAllowanceSlotKey(ownerAddress, spenderAddress, mappingSlotNumber); const slotKey = visitedSlotKeys.find(vk => vk === key); if (slotKey !== undefined) { // console.log(`slotNumber: ${mappingSlotNumber}`); allowanceSlotNumberByToken.set(this.address, mappingSlotNumber); super.stateDiff(slotKey); break; } } }); this._registeredPromises.push(promise); } else { super.stateDiff(getAllowanceSlotKey(ownerAddress, spenderAddress, mapSlotNumber)); } return this; } is(value) { if (this._method === undefined) { throw new Error(`Method to call of AsIf at ${this.address} is not set!`); } if (this._method === "balanceOf" || this._method === "allowance") { if (typeof value !== 'bigint') { throw new Error(`Invalid value for ${this._method} of AsIf at ${this.address}.`); } const slotValue = solidityPacked(["uint256"], [value]); super.is(slotValue); } return this; } } ; export const tokenAt = (address) => { return new TokenAsIf(address); }; export const exportedForTesting = { getBalanceOfSlotKey, getAllowanceSlotKey };