Skip to content

Instantly share code, notes, and snippets.

@Hero-Development
Last active August 26, 2023 19:10
Show Gist options
  • Save Hero-Development/62a13570ad627e106f8d9583a2f26164 to your computer and use it in GitHub Desktop.
Save Hero-Development/62a13570ad627e106f8d9583a2f26164 to your computer and use it in GitHub Desktop.
Ethers, account balance from storage
// 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();
}
}
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