mirror of
https://github.com/cerc-io/watcher-ts
synced 2025-01-06 19:38:05 +00:00
Get slot for ERC20 variable from storage layout (#13)
* Get slot for ERC20 variable from storage layout. * Fix solidity-mapper build for importing library functions. * Implement lint command in solidity-mapper package. Co-authored-by: nikugogoi <95nikass@gmail.com>
This commit is contained in:
parent
72ca980198
commit
a0aae09f83
5
packages/solidity-mapper/.eslintignore
Normal file
5
packages/solidity-mapper/.eslintignore
Normal file
@ -0,0 +1,5 @@
|
||||
# Don't lint node_modules.
|
||||
node_modules
|
||||
|
||||
# Don't lint build output.
|
||||
dist
|
20
packages/solidity-mapper/.eslintrc.json
Normal file
20
packages/solidity-mapper/.eslintrc.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": [
|
||||
"semistandard",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 12,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
}
|
||||
}
|
@ -17,17 +17,17 @@ $ yarn test
|
||||
|
||||
## Different Types
|
||||
|
||||
* Booleans
|
||||
* Integers
|
||||
* Fixed Point Numbers
|
||||
* Address
|
||||
* Contract Types
|
||||
* Fixed-size byte arrays
|
||||
* Enums
|
||||
* Function Types
|
||||
* Arrays
|
||||
* Structs
|
||||
* Mapping Types
|
||||
* [x] Booleans
|
||||
* [x] Integers
|
||||
* [ ] Fixed Point Numbers
|
||||
* [x] Address
|
||||
* [ ] Contract Types
|
||||
* [ ] Fixed-size byte arrays
|
||||
* [ ] Enums
|
||||
* [ ] Function Types
|
||||
* [ ] Arrays
|
||||
* [ ] Structs
|
||||
* [ ] Mapping Types
|
||||
|
||||
## Observations
|
||||
|
||||
|
@ -1,8 +1,7 @@
|
||||
{
|
||||
"name": "@vulcanize/solidity-mapper",
|
||||
"version": "0.1.0",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"main": "src/index.ts",
|
||||
"license": "UNLICENSED",
|
||||
"devDependencies": {
|
||||
"@nomiclabs/hardhat-ethers": "^2.0.2",
|
||||
@ -10,7 +9,16 @@
|
||||
"@types/chai": "^4.2.18",
|
||||
"@types/mocha": "^8.2.2",
|
||||
"@types/node": "^15.6.1",
|
||||
"@typescript-eslint/eslint-plugin": "^4.25.0",
|
||||
"@typescript-eslint/parser": "^4.25.0",
|
||||
"chai": "^4.3.4",
|
||||
"eslint": "^7.27.0",
|
||||
"eslint-config-semistandard": "^15.0.1",
|
||||
"eslint-config-standard": "^16.0.3",
|
||||
"eslint-plugin-import": "^2.23.3",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"eslint-plugin-standard": "^5.0.0",
|
||||
"ethereum-waffle": "^3.3.0",
|
||||
"ethers": "^5.2.0",
|
||||
"hardhat": "^2.3.0",
|
||||
@ -19,6 +27,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "hardhat test"
|
||||
"test": "hardhat test",
|
||||
"lint": "eslint ."
|
||||
}
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
export { getStorageValue, StorageLayout, GetStorageAt } from './storage';
|
||||
export { getStorageValue, getStorageInfo, StorageLayout, GetStorageAt } from './storage';
|
||||
|
@ -1,23 +1,50 @@
|
||||
import { utils, BigNumber } from 'ethers';
|
||||
|
||||
interface Storage {
|
||||
slot: string;
|
||||
offset: number;
|
||||
type: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface Type {
|
||||
encoding: string;
|
||||
numberOfBytes: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface StorageLayout {
|
||||
storage: [{
|
||||
slot: string;
|
||||
offset: number;
|
||||
type: string;
|
||||
label: string;
|
||||
}];
|
||||
types: {
|
||||
[type: string]: {
|
||||
encoding: string;
|
||||
numberOfBytes: string;
|
||||
label: string;
|
||||
}
|
||||
};
|
||||
storage: Storage[];
|
||||
types: { [type: string]: Type; }
|
||||
}
|
||||
|
||||
export interface StorageInfo extends Storage {
|
||||
types: { [type: string]: Type; }
|
||||
}
|
||||
|
||||
export type GetStorageAt = (address: string, position: string) => Promise<string>
|
||||
|
||||
/**
|
||||
* Function to get storage information of variable from storage layout.
|
||||
* @param storageLayout
|
||||
* @param variableName
|
||||
*/
|
||||
export const getStorageInfo = (storageLayout: StorageLayout, variableName: string): StorageInfo => {
|
||||
const { storage, types } = storageLayout;
|
||||
const targetState = storage.find((state) => state.label === variableName)
|
||||
|
||||
// Throw if state variable could not be found in storage layout.
|
||||
if (!targetState) {
|
||||
throw new Error('Variable not present in storage layout.');
|
||||
}
|
||||
|
||||
return {
|
||||
...targetState,
|
||||
slot: utils.hexlify(BigNumber.from(targetState.slot)),
|
||||
types
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to get the value from storage for a contract variable.
|
||||
* @param address
|
||||
@ -26,15 +53,7 @@ export type GetStorageAt = (address: string, position: string) => Promise<string
|
||||
* @param variableName
|
||||
*/
|
||||
export const getStorageValue = async (address: string, storageLayout: StorageLayout, getStorageAt: GetStorageAt, variableName: string): Promise<number | string | boolean | undefined> => {
|
||||
const { storage, types } = storageLayout;
|
||||
const targetState = storage.find((state) => state.label === variableName)
|
||||
|
||||
// Return if state variable could not be found in storage layout.
|
||||
if (!targetState) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { slot, offset, type } = targetState;
|
||||
const { slot, offset, type, types } = getStorageInfo(storageLayout, variableName);
|
||||
const { encoding, numberOfBytes, label } = types[type]
|
||||
|
||||
// Get value according to encoding i.e. how the data is encoded in storage.
|
||||
@ -79,7 +98,7 @@ export const getStorageValue = async (address: string, storageLayout: StorageLay
|
||||
* @param getStorageAt
|
||||
*/
|
||||
const getInplaceArray = async (address: string, slot: string, offset: number, numberOfBytes: string, getStorageAt: GetStorageAt) => {
|
||||
const value = await getStorageAt(address, BigNumber.from(slot).toHexString());
|
||||
const value = await getStorageAt(address, slot);
|
||||
const uintArray = utils.arrayify(value);
|
||||
|
||||
// Get value according to offset.
|
||||
@ -97,7 +116,7 @@ const getInplaceArray = async (address: string, slot: string, offset: number, nu
|
||||
* @param getStorageAt
|
||||
*/
|
||||
const getBytesArray = async (address: string, slot: string, getStorageAt: GetStorageAt) => {
|
||||
let value = await getStorageAt(address, BigNumber.from(slot).toHexString());
|
||||
let value = await getStorageAt(address, slot);
|
||||
const uintArray = utils.arrayify(value);
|
||||
let length = 0;
|
||||
|
||||
@ -122,8 +141,8 @@ const getBytesArray = async (address: string, slot: string, getStorageAt: GetSto
|
||||
|
||||
// Compute zero padded hexstring to calculate hashed position of storage.
|
||||
// https://github.com/ethers-io/ethers.js/issues/1079#issuecomment-703056242
|
||||
const slotHex = utils.hexZeroPad(BigNumber.from(slot).toHexString(), 32);
|
||||
const position = utils.keccak256(slotHex);
|
||||
const paddedSlotHex = utils.hexZeroPad(slot, 32);
|
||||
const position = utils.keccak256(paddedSlotHex);
|
||||
|
||||
// Get value from consecutive storage slots for longer data.
|
||||
for(let i = 0; i < length / 32; i++) {
|
||||
|
@ -70,5 +70,5 @@
|
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"],
|
||||
"exclude": ["src/**/*.test.ts"]
|
||||
}
|
||||
|
@ -22,6 +22,7 @@
|
||||
"@types/lodash": "^4.14.168",
|
||||
"@vulcanize/cache": "^0.1.0",
|
||||
"@vulcanize/ipld-eth-client": "^0.1.0",
|
||||
"@vulcanize/solidity-mapper": "^0.1.0",
|
||||
"apollo-type-bigint": "^0.1.3",
|
||||
"canonical-json": "^0.0.4",
|
||||
"debug": "^4.3.1",
|
||||
|
365
packages/watcher/src/artifacts/ERC20.json
Normal file
365
packages/watcher/src/artifacts/ERC20.json
Normal file
@ -0,0 +1,365 @@
|
||||
{
|
||||
"abi": [
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "name_",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "symbol_",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "constructor"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Approval",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Transfer",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "allowance",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "approve",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "account",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "balanceOf",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "decimals",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint8",
|
||||
"name": "",
|
||||
"type": "uint8"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "subtractedValue",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "decreaseAllowance",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "addedValue",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "increaseAllowance",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "name",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "symbol",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "totalSupply",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "recipient",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transfer",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "sender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "recipient",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transferFrom",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
],
|
||||
"storageLayout": {
|
||||
"storage": [
|
||||
{
|
||||
"astId": 15,
|
||||
"contract": "@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20",
|
||||
"label": "_balances",
|
||||
"offset": 0,
|
||||
"slot": "0",
|
||||
"type": "t_mapping(t_address,t_uint256)"
|
||||
},
|
||||
{
|
||||
"astId": 21,
|
||||
"contract": "@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20",
|
||||
"label": "_allowances",
|
||||
"offset": 0,
|
||||
"slot": "1",
|
||||
"type": "t_mapping(t_address,t_mapping(t_address,t_uint256))"
|
||||
},
|
||||
{
|
||||
"astId": 23,
|
||||
"contract": "@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20",
|
||||
"label": "_totalSupply",
|
||||
"offset": 0,
|
||||
"slot": "2",
|
||||
"type": "t_uint256"
|
||||
},
|
||||
{
|
||||
"astId": 25,
|
||||
"contract": "@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20",
|
||||
"label": "_name",
|
||||
"offset": 0,
|
||||
"slot": "3",
|
||||
"type": "t_string_storage"
|
||||
},
|
||||
{
|
||||
"astId": 27,
|
||||
"contract": "@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20",
|
||||
"label": "_symbol",
|
||||
"offset": 0,
|
||||
"slot": "4",
|
||||
"type": "t_string_storage"
|
||||
}
|
||||
],
|
||||
"types": {
|
||||
"t_address": {
|
||||
"encoding": "inplace",
|
||||
"label": "address",
|
||||
"numberOfBytes": "20"
|
||||
},
|
||||
"t_mapping(t_address,t_mapping(t_address,t_uint256))": {
|
||||
"encoding": "mapping",
|
||||
"key": "t_address",
|
||||
"label": "mapping(address => mapping(address => uint256))",
|
||||
"numberOfBytes": "32",
|
||||
"value": "t_mapping(t_address,t_uint256)"
|
||||
},
|
||||
"t_mapping(t_address,t_uint256)": {
|
||||
"encoding": "mapping",
|
||||
"key": "t_address",
|
||||
"label": "mapping(address => uint256)",
|
||||
"numberOfBytes": "32",
|
||||
"value": "t_uint256"
|
||||
},
|
||||
"t_string_storage": {
|
||||
"encoding": "bytes",
|
||||
"label": "string",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_uint256": {
|
||||
"encoding": "inplace",
|
||||
"label": "uint256",
|
||||
"numberOfBytes": "32"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,11 +4,9 @@ import debug from 'debug';
|
||||
|
||||
import { getCache } from '@vulcanize/cache';
|
||||
import { EthClient, getMappingSlot, topictoAddress } from '@vulcanize/ipld-eth-client';
|
||||
import { getStorageInfo } from '@vulcanize/solidity-mapper';
|
||||
|
||||
// Event slots.
|
||||
// TODO: Read from storage layout file.
|
||||
const ERC20_BALANCE_OF_SLOT = "0x00";
|
||||
const ERC20_ALLOWANCE_SLOT = "0x01";
|
||||
import { storageLayout } from './artifacts/ERC20.json';
|
||||
|
||||
// Event signatures.
|
||||
// TODO: Generate from ABI.
|
||||
@ -55,7 +53,8 @@ export const createResolvers = async (config) => {
|
||||
balanceOf: async (_, { blockHash, token, owner }) => {
|
||||
log('balanceOf', blockHash, token, owner);
|
||||
|
||||
const slot = getMappingSlot(ERC20_BALANCE_OF_SLOT, owner);
|
||||
const { slot: balancesSlot } = getStorageInfo(storageLayout, '_balances')
|
||||
const slot = getMappingSlot(balancesSlot, owner);
|
||||
|
||||
const vars = {
|
||||
blockHash,
|
||||
@ -89,7 +88,8 @@ export const createResolvers = async (config) => {
|
||||
allowance: async (_, { blockHash, token, owner, spender }) => {
|
||||
log('allowance', blockHash, token, owner, spender);
|
||||
|
||||
const slot = getMappingSlot(getMappingSlot(ERC20_ALLOWANCE_SLOT, owner), spender);
|
||||
const { slot: allowancesSlot } = getStorageInfo(storageLayout, '_allowances')
|
||||
const slot = getMappingSlot(getMappingSlot(allowancesSlot, owner), spender);
|
||||
|
||||
const vars = {
|
||||
blockHash,
|
||||
|
@ -66,6 +66,7 @@
|
||||
|
||||
/* Advanced Options */
|
||||
"skipLibCheck": true, /* Skip type checking of declaration files. */
|
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||
"forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
|
||||
"resolveJsonModule": true /* Enabling the option allows importing JSON, and validating the types in that JSON file. */
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user