Last active
August 26, 2023 19:10
-
-
Save Hero-Development/62a13570ad627e106f8d9583a2f26164 to your computer and use it in GitHub Desktop.
Ethers, account balance from storage
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: BSD-3-Clause | |
pragma solidity ^0.8.17; | |
import { EnumerableMap } from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; | |
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; | |
abstract contract ERC165 { | |
// slot 0 | |
mapping(bytes4 => bool) private _supportedInterfaces; | |
} | |
contract ERC721 is ERC165 { | |
using EnumerableSet for EnumerableSet.UintSet; | |
using EnumerableMap for EnumerableMap.UintToAddressMap; | |
// doesn't count | |
bytes4 private constant _ERC721_RECEIVED = 0x150b7a02; | |
// slot 1: bingo | |
mapping (address => EnumerableSet.UintSet) private _holderTokens; | |
// slot 2: array (index => address) | |
EnumerableMap.UintToAddressMap private _tokenOwners; | |
// CEE: lots of stuff deleted | |
// CEE: proof... | |
function balanceOf(address owner) public view virtual returns (uint256) { | |
require(owner != address(0), "ERC721: balance query for the zero address"); | |
return _holderTokens[owner].length(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { assert, ethers, Web3 } = require("hardhat"); | |
const { getABI } = require("./lib/helpers"); | |
const toEvmWord = (val) => { | |
const t = typeof val; | |
if(t === 'number'){ | |
return '0x'+ val.toString(16).padStart(64, '0'); | |
} | |
if(t === 'bigint'){ | |
return '0x'+ val.toString(16).padStart(64, '0'); | |
} | |
if(val instanceof BigInt){ | |
return '0x'+ val.toString(16).padStart(64, '0'); | |
} | |
console.warn({ t, val }); | |
return Web3.utils.padLeft(val, 64); | |
} | |
// CEE: these tests require mainnet forking... | |
contract( 'BAYC Storage', () => { | |
let ownerSigner; | |
const signers = []; | |
const baycAddress = '0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D'; | |
it( "loads accounts", async () => { | |
signers.push( ...(await ethers.getSigners()) ); | |
ownerSigner = signers.shift(); | |
}); | |
let bayc; | |
it( "loads BAYC", async () => { | |
const implAbi = await getABI(baycAddress, {setCache: true, useCache: true}); | |
bayc = new ethers.Contract(baycAddress, implAbi, ownerSigner); | |
}); | |
// slot 2: array (index => address) | |
// EnumerableMap.UintToAddressMap private _tokenOwners; | |
it( "verifies slot 2 is _tokenOwners", async () => { | |
const totalSupply = await bayc.totalSupply(); | |
assert.equal(totalSupply, "10000"); | |
// console.log({ totalSupply }); | |
const value = await ethers.provider.getStorageAt(baycAddress, 2); | |
assert.equal(BigInt(value), BigInt(totalSupply)); | |
}); | |
it("verify owner of token 0", async () => { | |
const tokenId = 0 | |
const ownerOf0 = await bayc.ownerOf(tokenId); | |
// when data is dynamic, slot data is offset by its keccak hash | |
const tokenOwnerSlot = 2; | |
const tokenOwnerSlotWord = toEvmWord(tokenOwnerSlot); | |
const startSlot = ethers.utils.keccak256(tokenOwnerSlotWord); | |
// dynamic values are offset by 1 | |
const valueSlot = BigInt(startSlot) + BigInt(tokenId + 1); | |
const valueSlotWord = toEvmWord(valueSlot); | |
const slotValue = await ethers.provider.getStorageAt(baycAddress, valueSlotWord); | |
console.log({ ownerOf0, slotValue }); | |
assert.equal(BigInt(ownerOf0), BigInt(slotValue)); | |
}); | |
it("gets balance", async () => { | |
const tokenId = 0 | |
const ownerOf0 = await bayc.ownerOf(tokenId); | |
const balanceOf = await bayc.balanceOf(ownerOf0); | |
// when data is dynamic, slot data is offset by its keccak hash | |
const balancesSlot = 1; | |
// const balancesSlotWord = toEvmWord(balancesSlot); | |
// const startSlot = ethers.utils.keccak256(balancesSlotWord); | |
// https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html | |
// The value corresponding to a mapping key k is located at `keccak256(h(k) . p)` where: | |
// - `.` is concatenation | |
// - `h` is a function that is applied to the key depending on its type | |
// - for value types, h pads the value to 32 bytes in the same way as when storing the value in memory. | |
// - for strings and byte arrays, h(k) is just the unpadded data | |
// h(k) | |
const owner0word = toEvmWord(ownerOf0); | |
//TODO: concat function | |
const concat = '0x' | |
+ BigInt(owner0word).toString(16).padStart(64, '0') | |
+ BigInt(balancesSlot).toString(16).padStart(64, '0'); | |
// console.log({ | |
// owner0word, | |
// balancesSlotWord, | |
// concat | |
// }); | |
const concatHash = ethers.utils.keccak256(concat); | |
const concatHashWord = toEvmWord(concatHash); | |
const slotValue2 = await ethers.provider.getStorageAt(baycAddress, concatHashWord); | |
console.log({ balanceOf, slotValue2 }); | |
assert.equal(BigInt(balanceOf), BigInt(slotValue2)); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment