From 2a2de9e0c2c8f095a8f49e8c0789cddf863df632 Mon Sep 17 00:00:00 2001 From: d3or Date: Sat, 16 Nov 2024 13:25:24 +0700 Subject: [PATCH 1/4] feat: add permit2 allowance storage slot utils --- src/index.ts | 4 + src/permit2.ts | 69 +++++++++++++++++ tests/integration/mockPermit2Approval.test.ts | 75 +++++++++++++++++++ tests/unit/approvals/getErc20Approval.test.ts | 2 +- tests/unit/balances/getErc20Balance.test.ts | 2 +- .../getErc20BalanceStorageSlot.test.ts | 2 +- ...computePermit2AllowanceStorageSlot.test.ts | 15 ++++ .../permit2/getPermit2Erc20Allowance.test.ts | 45 +++++++++++ 8 files changed, 211 insertions(+), 3 deletions(-) create mode 100644 src/permit2.ts create mode 100644 tests/integration/mockPermit2Approval.test.ts create mode 100644 tests/unit/permit2/computePermit2AllowanceStorageSlot.test.ts create mode 100644 tests/unit/permit2/getPermit2Erc20Allowance.test.ts diff --git a/src/index.ts b/src/index.ts index 85f95ac..4c7c58c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,6 +10,8 @@ import { getErc20BalanceStorageSlot, } from "./balance"; +import { computePermit2AllowanceStorageSlot, getPermit2ERC20Allowance } from "./permit2" + export { approvalCache, balanceCache, @@ -19,4 +21,6 @@ export { getErc20BalanceStorageSlot, getErc20Approval, getErc20Balance, + getPermit2ERC20Allowance, + computePermit2AllowanceStorageSlot }; diff --git a/src/permit2.ts b/src/permit2.ts new file mode 100644 index 0000000..cf1f768 --- /dev/null +++ b/src/permit2.ts @@ -0,0 +1,69 @@ +import { ethers } from "ethers"; + + +/** + * Compute the storage slot for permit2 allowance. + * NOTE: unlike arbitrary erc20 contracts, we know the slot for where this is stored (1) :) + * + * @param erc20Address - The address of the ERC20 token + * @param ownerAddress - The address of the ERC20 token owner + * @param spenderAddress - The address of the spender + * @returns The slot where the allowance amount is stored, mock this + * + * - This uses a brute force approach similar to the balance slot search. See the balance slot search comment for more details. + */ +export const computePermit2AllowanceStorageSlot = (ownerAddress: string, erc20Address: string, spenderAddress: string): { + slot: string; + slotHash: string; + slotHash2: string; +} => { + + // Calculate the slot hash, using the owner address and the slot index (1) + const slotHash = ethers.utils.keccak256( + ethers.utils.defaultAbiCoder.encode( + ["address", "uint256"], + [ownerAddress, 1] + ) + ); + + // Calcualte the storage slot hash for spender slot + const slotHash2 = ethers.utils.keccak256( + ethers.utils.defaultAbiCoder.encode( + ["address", "bytes32"], + [erc20Address, slotHash] + ) + ); + // Calculate the final storage slot to mock, using the spender address and the slot hash2 + const slot = ethers.utils.keccak256( + ethers.utils.defaultAbiCoder.encode( + ["address", "bytes32"], + [spenderAddress, slotHash2] + ) + ); + return { slot, slotHash, slotHash2 } +} + + +/** + * Get the permit2 erc20 allowance for a given ERC20 token and spender + * @param provider - The JsonRpcProvider instance + * @param permit2Address - The permit2 contract address + * @param erc20Address - The address of the ERC20 token + * @param ownerAddress - The address of the ERC20 token owner + * @param spenderAddress - The address of the spender + * @returns The approval amount + */ +export const getPermit2ERC20Allowance = async ( + provider: ethers.providers.JsonRpcProvider, + permit2Address: string, + ownerAddress: string, erc20Address: string, spenderAddress: string): Promise => { + const contract = new ethers.Contract( + permit2Address, + [ + "function allowance(address owner, address token, address spender) view returns (uint256)", + ], + provider + ); + const approval = await contract.allowance(ownerAddress, erc20Address, spenderAddress); + return approval; +}; diff --git a/tests/integration/mockPermit2Approval.test.ts b/tests/integration/mockPermit2Approval.test.ts new file mode 100644 index 0000000..7000a68 --- /dev/null +++ b/tests/integration/mockPermit2Approval.test.ts @@ -0,0 +1,75 @@ +import { ethers } from "ethers"; +import { computePermit2AllowanceStorageSlot, getPermit2ERC20Allowance } from "../../src"; + +describe("mockErc20Approval", () => { + const baseProvider = new ethers.providers.JsonRpcProvider( + process.env.BASE_RPC_URL ?? "https://localhost:8545" + ); + + const mockAddress = ethers.Wallet.createRandom().address; + + it("should mock a random address to have a permit2 allowance", async () => { + const tokenAddress = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"; + const spenderAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; + const mockApprovalAmount = + "1461501637330902918203684832716283019655932142975"; + const mockApprovalHex = ethers.utils.hexZeroPad( + ethers.utils.hexlify(ethers.BigNumber.from(mockApprovalAmount)), + 32 + ) + const permit2Contract = '0x000000000022d473030f116ddee9f6b43ac78ba3' + + + const permit2Slot = computePermit2AllowanceStorageSlot(mockAddress, tokenAddress, spenderAddress) + expect(permit2Slot.slot).toBeDefined() + + // get approval of spenderAddress before, to make sure its 0 before we mock it + const approvalBefore = await getPermit2ERC20Allowance( + baseProvider, + permit2Contract, + mockAddress, + tokenAddress, + spenderAddress + ); + expect(approvalBefore.toString()).toBe("0"); + + // Create the stateDiff object + const stateDiff = { + [permit2Contract]: { + stateDiff: { + [permit2Slot.slot]: mockApprovalHex, + }, + }, + }; + + // 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 approval = ethers.BigNumber.from( + ethers.utils.defaultAbiCoder.decode(["uint256"], allowanceResponse)[0] + ); + + // check the approval + expect(approval.eq(mockApprovalAmount)).toBe(true); + }, 30000); +}); diff --git a/tests/unit/approvals/getErc20Approval.test.ts b/tests/unit/approvals/getErc20Approval.test.ts index dab4ebe..3393d0c 100644 --- a/tests/unit/approvals/getErc20Approval.test.ts +++ b/tests/unit/approvals/getErc20Approval.test.ts @@ -22,7 +22,7 @@ describe("getErc20Approval", () => { ); expect(approval).toBeDefined(); expect(approval.toString()).toBe( - "1461501637330902918203684832716283019655932142975" + "1461501637330902918203684832716283019655931142975" ); }, 30000); diff --git a/tests/unit/balances/getErc20Balance.test.ts b/tests/unit/balances/getErc20Balance.test.ts index 3d8bb5a..df4279c 100644 --- a/tests/unit/balances/getErc20Balance.test.ts +++ b/tests/unit/balances/getErc20Balance.test.ts @@ -19,7 +19,7 @@ describe("getErc20Balance", () => { ownerAddress ); expect(balance).toBeDefined(); - expect(balance.toString()).toBe("9600000"); + expect(balance.toString()).toBe("8600000"); }, 30000); it("[vyper] should return the balance for the owner", async () => { diff --git a/tests/unit/balances/getErc20BalanceStorageSlot.test.ts b/tests/unit/balances/getErc20BalanceStorageSlot.test.ts index 9ab2e8d..05e9cac 100644 --- a/tests/unit/balances/getErc20BalanceStorageSlot.test.ts +++ b/tests/unit/balances/getErc20BalanceStorageSlot.test.ts @@ -23,7 +23,7 @@ describe("getErc20BalanceStorageSlot", () => { expect(slot).toBeDefined(); expect(balance).toBeDefined(); expect(slot).toBe("0x09"); - expect(balance.toString()).toBe("9600000"); + expect(balance.toString()).toBe("8600000"); expect(isVyper).toBe(false); }, 30000); diff --git a/tests/unit/permit2/computePermit2AllowanceStorageSlot.test.ts b/tests/unit/permit2/computePermit2AllowanceStorageSlot.test.ts new file mode 100644 index 0000000..1dcc43a --- /dev/null +++ b/tests/unit/permit2/computePermit2AllowanceStorageSlot.test.ts @@ -0,0 +1,15 @@ +import { computePermit2AllowanceStorageSlot } from "../../../src"; + +describe("computePermit2AllowanceStorageSlot", () => { + it("should compute a permit2 allowance storage slot", () => { + const tokenAddress = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"; + const ownerAddress = "0x0000c3Caa36E2d9A8CD5269C976eDe05018f0000"; + const spenderAddress = "0x3e34b27a9bf37D8424e1a58aC7fc4D06914B76B9"; + + const data = computePermit2AllowanceStorageSlot(ownerAddress, tokenAddress, spenderAddress); + expect(data).toBeDefined(); + expect(data.slot).toBeDefined(); + expect(data.slot).toBe("0x661d04a618eb4f8048d934a2379f8d7627537668a69b0c43512058a631e31c57") + }, 30000); + +}); diff --git a/tests/unit/permit2/getPermit2Erc20Allowance.test.ts b/tests/unit/permit2/getPermit2Erc20Allowance.test.ts new file mode 100644 index 0000000..a7da94d --- /dev/null +++ b/tests/unit/permit2/getPermit2Erc20Allowance.test.ts @@ -0,0 +1,45 @@ +import { computePermit2AllowanceStorageSlot, getPermit2ERC20Allowance } from "../../../src"; +import { ethers } from "ethers"; + +describe("getPermit2ERC20Allowance", () => { + const baseProvider = new ethers.providers.JsonRpcProvider( + process.env.BASE_RPC_URL ?? "https://localhost:8545" + ); + + it("should return the permit2 ERC20 allowance for the owner", async () => { + const tokenAddress = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"; + const ownerAddress = "0x0000c3Caa36E2d9A8CD5269C976eDe05018f0000"; + const spenderAddress = "0x0000000000000000000000000000000000000000"; + + const permit2Contract = '0x000000000022d473030f116ddee9f6b43ac78ba3' + const allowance = await getPermit2ERC20Allowance( + baseProvider, + permit2Contract, + ownerAddress, + tokenAddress, + spenderAddress + ); + console.log(computePermit2AllowanceStorageSlot(ownerAddress, tokenAddress, spenderAddress)) + expect(allowance).toBeDefined(); + expect(allowance.toString()).toBe("1"); + + }, 30000); + + it("should return 0 allowance for permit2 ERC20 allowance for vitalik", async () => { + const tokenAddress = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"; + const ownerAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; + const spenderAddress = "0x0000000000000000000000000000000000000000"; + + const permit2Contract = '0x000000000022d473030f116ddee9f6b43ac78ba3' + const allowance = await getPermit2ERC20Allowance( + baseProvider, + permit2Contract, + ownerAddress, + tokenAddress, + spenderAddress + ); + expect(allowance).toBeDefined(); + expect(allowance.toString()).toBe("0"); + }, 30000); + +}); From 05f139020246b8b99e222fc8ef6e58123a040d12 Mon Sep 17 00:00:00 2001 From: d3or Date: Sat, 16 Nov 2024 14:11:25 +0700 Subject: [PATCH 2/4] feat: add getStorageAt to compute permit2 allowance test --- src/permit2.ts | 12 +++++------- .../computePermit2AllowanceStorageSlot.test.ts | 18 +++++++++++++++--- .../permit2/getPermit2Erc20Allowance.test.ts | 1 - 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/permit2.ts b/src/permit2.ts index cf1f768..6f12a10 100644 --- a/src/permit2.ts +++ b/src/permit2.ts @@ -14,12 +14,10 @@ import { ethers } from "ethers"; */ export const computePermit2AllowanceStorageSlot = (ownerAddress: string, erc20Address: string, spenderAddress: string): { slot: string; - slotHash: string; - slotHash2: string; } => { // Calculate the slot hash, using the owner address and the slot index (1) - const slotHash = ethers.utils.keccak256( + const ownerSlotHash = ethers.utils.keccak256( ethers.utils.defaultAbiCoder.encode( ["address", "uint256"], [ownerAddress, 1] @@ -27,20 +25,20 @@ export const computePermit2AllowanceStorageSlot = (ownerAddress: string, erc20Ad ); // Calcualte the storage slot hash for spender slot - const slotHash2 = ethers.utils.keccak256( + const tokenSlotHash = ethers.utils.keccak256( ethers.utils.defaultAbiCoder.encode( ["address", "bytes32"], - [erc20Address, slotHash] + [erc20Address, ownerSlotHash] ) ); // Calculate the final storage slot to mock, using the spender address and the slot hash2 const slot = ethers.utils.keccak256( ethers.utils.defaultAbiCoder.encode( ["address", "bytes32"], - [spenderAddress, slotHash2] + [spenderAddress, tokenSlotHash] ) ); - return { slot, slotHash, slotHash2 } + return { slot } } diff --git a/tests/unit/permit2/computePermit2AllowanceStorageSlot.test.ts b/tests/unit/permit2/computePermit2AllowanceStorageSlot.test.ts index 1dcc43a..346c05c 100644 --- a/tests/unit/permit2/computePermit2AllowanceStorageSlot.test.ts +++ b/tests/unit/permit2/computePermit2AllowanceStorageSlot.test.ts @@ -1,15 +1,27 @@ +import { ethers } from "ethers"; import { computePermit2AllowanceStorageSlot } from "../../../src"; describe("computePermit2AllowanceStorageSlot", () => { - it("should compute a permit2 allowance storage slot", () => { + it("should compute a permit2 allowance storage slot", async () => { const tokenAddress = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"; const ownerAddress = "0x0000c3Caa36E2d9A8CD5269C976eDe05018f0000"; - const spenderAddress = "0x3e34b27a9bf37D8424e1a58aC7fc4D06914B76B9"; + const spenderAddress = "0x0000000000000000000000000000000000000000"; + + const permit2Contract = '0x000000000022d473030f116ddee9f6b43ac78ba3' const data = computePermit2AllowanceStorageSlot(ownerAddress, tokenAddress, spenderAddress); expect(data).toBeDefined(); expect(data.slot).toBeDefined(); - expect(data.slot).toBe("0x661d04a618eb4f8048d934a2379f8d7627537668a69b0c43512058a631e31c57") + expect(data.slot).toBe("0x31c9cad297553b4448680116a2d90c11b601cf1811034cd3bbe54da53c870184") + + const baseProvider = new ethers.providers.JsonRpcProvider( + process.env.BASE_RPC_URL ?? "https://localhost:8545" + ); + + const valueAtStorageSlot = await baseProvider.getStorageAt(permit2Contract, data.slot) + + expect(valueAtStorageSlot).toBeDefined() + expect(valueAtStorageSlot).toBe('0x00000000000001aa7be40acd0000000000000000000000000000000000000001') }, 30000); }); diff --git a/tests/unit/permit2/getPermit2Erc20Allowance.test.ts b/tests/unit/permit2/getPermit2Erc20Allowance.test.ts index a7da94d..8eeb540 100644 --- a/tests/unit/permit2/getPermit2Erc20Allowance.test.ts +++ b/tests/unit/permit2/getPermit2Erc20Allowance.test.ts @@ -19,7 +19,6 @@ describe("getPermit2ERC20Allowance", () => { tokenAddress, spenderAddress ); - console.log(computePermit2AllowanceStorageSlot(ownerAddress, tokenAddress, spenderAddress)) expect(allowance).toBeDefined(); expect(allowance.toString()).toBe("1"); From 8c3b7f3e7e50ddc6e75f63d3bc8aae0ea10e7a56 Mon Sep 17 00:00:00 2001 From: d3or Date: Sat, 16 Nov 2024 14:46:34 +0700 Subject: [PATCH 3/4] feat: bump semver minor version --- README.md | 77 ++++++++++++++++++++++++++++++++++++++++++++++++-- package.json | 2 +- src/permit2.ts | 65 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0bf9d6b..027fec7 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Follow on Twitter Build Status -slotseek is a javascript library that assists with finding the storage slots for the `balanceOf` and `allowance` mappings in an ERC20 token contract. It also provides a way to generate mock data that can be used to override the state of a contract in an `eth_call` or `eth_estimateGas` call. +slotseek is a javascript library that assists with finding the storage slots for the `balanceOf` and `allowance` mappings in an ERC20 token contract, and the permit2 allowance mapping. It also provides a way to generate mock data that can be used to override the state of a contract in an `eth_call` or `eth_estimateGas` call. The main use case for this library is to estimate gas costs of transactions that would fail if the address did not have the required balance or approval. @@ -12,7 +12,7 @@ For example, estimating the gas a transaction will consume when swapping, before ## Features -- Find storage slots for `balanceOf` and `allowance` mappings in an ERC20 token contract +- Find storage slots for `balanceOf` and `allowance` mappings in an ERC20 token contract, and permit2 allowance mapping - Generates mock data that can be used to override the state of a contract in an `eth_call`/`eth_estimateGas` call - Supports [vyper storage layouts](https://docs.vyperlang.org/en/stable/scoping-and-declarations.html#storage-layout) @@ -209,3 +209,76 @@ async function findStorageSlot() { findStorageSlot().catch(console.error); ``` + +## Example of mocking the permit2 allowance mapping + +```javascript +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); +``` diff --git a/package.json b/package.json index ab21eda..35d6688 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@d3or/slotseek", - "version": "1.0.2", + "version": "1.1.2", "description": "A library for finding the storage slots on an ERC20 token for balances and approvals, which can be used to mock the balances and approvals of an address when estimating gas costs of transactions that would fail if the address did not have the required balance or approval", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/permit2.ts b/src/permit2.ts index 6f12a10..ee1b1df 100644 --- a/src/permit2.ts +++ b/src/permit2.ts @@ -65,3 +65,68 @@ export const getPermit2ERC20Allowance = async ( const approval = await contract.allowance(ownerAddress, erc20Address, spenderAddress); return approval; }; + +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 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 approval = ethers.BigNumber.from( + ethers.utils.defaultAbiCoder.decode(["uint256"], allowanceResponse)[0] + ); + + console.log( + `Mocked balance for ${mockAddress}: ${ethers.utils.formatUnits( + approval, + 6 + )} USDC` + ); + +} From ec6f909ff058e2c28bd2b69a04b7e199500d4c54 Mon Sep 17 00:00:00 2001 From: d3or Date: Sat, 16 Nov 2024 14:48:20 +0700 Subject: [PATCH 4/4] fix: remove left over code in permit2 --- src/permit2.ts | 63 -------------------------------------------------- 1 file changed, 63 deletions(-) diff --git a/src/permit2.ts b/src/permit2.ts index ee1b1df..67c1b63 100644 --- a/src/permit2.ts +++ b/src/permit2.ts @@ -66,67 +66,4 @@ export const getPermit2ERC20Allowance = async ( return approval; }; -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 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 approval = ethers.BigNumber.from( - ethers.utils.defaultAbiCoder.decode(["uint256"], allowanceResponse)[0] - ); - - console.log( - `Mocked balance for ${mockAddress}: ${ethers.utils.formatUnits( - approval, - 6 - )} USDC` - ); - -}