mirror of
https://github.com/cerc-io/watcher-ts
synced 2024-11-19 20:36:19 +00:00
Get value for array of integer and boolean types in solidity mapper (#30)
* Implement getting value for array type. * Add subtypes in solidit mapper readme. Co-authored-by: nikugogoi <95nikass@gmail.com>
This commit is contained in:
parent
00eb129536
commit
23f9a9db41
@ -17,20 +17,49 @@ $ yarn test
|
||||
|
||||
## Different Types
|
||||
|
||||
* [x] Booleans
|
||||
* [x] Integers
|
||||
* [ ] Fixed Point Numbers
|
||||
* [x] Address
|
||||
* [x] Contract Types
|
||||
* [x] Fixed-size byte arrays
|
||||
* [x] Enums
|
||||
* [ ] Function Types
|
||||
* [ ] Arrays
|
||||
* [ ] Dynamically-sized byte array
|
||||
* [ ] Bytes
|
||||
* [x] String
|
||||
* [ ] Structs
|
||||
* [ ] Mapping Types
|
||||
* [ ] Value Types
|
||||
* [x] Booleans
|
||||
* [x] Integers
|
||||
* [ ] Fixed Point Numbers
|
||||
* [x] Address
|
||||
* [x] Contract Types
|
||||
* [x] Fixed-size byte arrays
|
||||
* [x] Enums
|
||||
* [ ] Function Types
|
||||
* [ ] Reference Types
|
||||
* [ ] Arrays
|
||||
* [ ] Fixed size arrays
|
||||
* [x] Integer Type
|
||||
* [x] Boolean Type
|
||||
* [ ] Address Type
|
||||
* [ ] Fixed-size byte arrays
|
||||
* [ ] Enum type
|
||||
* [ ] Dynamically-sized byte array
|
||||
* [ ] Struct Type
|
||||
* [ ] Mapping Type
|
||||
* [ ] Dynamically-sized arrays
|
||||
* [ ] Integer Type
|
||||
* [ ] Boolean Type
|
||||
* [ ] Address Type
|
||||
* [ ] Fixed-size byte arrays
|
||||
* [ ] Enum Type
|
||||
* [ ] Dynamically-sized byte array
|
||||
* [ ] Struct Type
|
||||
* [ ] Mapping Type
|
||||
* [ ] Nested Arrays
|
||||
* [ ] Fixed size arrays
|
||||
* [ ] Dynamically-sized arrays
|
||||
* [ ] Dynamically-sized byte array
|
||||
* [ ] Bytes
|
||||
* [x] String
|
||||
* [ ] Structs
|
||||
* [ ] Value Types
|
||||
* [ ] Reference Types
|
||||
* [ ] Mapping Types
|
||||
* [ ] Value Type keys
|
||||
* [ ] Dynamically-sized byte array keys
|
||||
* [ ] Reference Type Mapping values
|
||||
* [ ] Nested Mapping
|
||||
|
||||
## Observations
|
||||
|
||||
|
@ -29,5 +29,6 @@
|
||||
"build": "tsc",
|
||||
"test": "hardhat test",
|
||||
"lint": "eslint ."
|
||||
}
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ const TEST_DATA = [
|
||||
}
|
||||
];
|
||||
|
||||
it('get event name topics', async function () {
|
||||
it('get event name topics', async () => {
|
||||
const testPromises = TEST_DATA.map(async ({ name, output }) => {
|
||||
const Contract = await ethers.getContractFactory(name);
|
||||
const contract = await Contract.deploy();
|
||||
|
@ -59,7 +59,7 @@ const TEST_DATA = [
|
||||
}
|
||||
];
|
||||
|
||||
it('get storage information', async function () {
|
||||
it('get storage information', async () => {
|
||||
const testPromises = TEST_DATA.map(async ({ name, variable, output }) => {
|
||||
const Contract = await ethers.getContractFactory(name);
|
||||
const contract = await Contract.deploy();
|
||||
@ -73,14 +73,14 @@ it('get storage information', async function () {
|
||||
await Promise.all(testPromises);
|
||||
});
|
||||
|
||||
describe('Get value from storage', function () {
|
||||
describe('Get value from storage', () => {
|
||||
const getBlockHash = async () => {
|
||||
const blockNumber = await ethers.provider.getBlockNumber();
|
||||
const { hash } = await ethers.provider.getBlock(blockNumber);
|
||||
return hash;
|
||||
};
|
||||
|
||||
it('get value for integer type variables packed together', async function () {
|
||||
it('get value for integer type variables packed together', async () => {
|
||||
const Integers = await ethers.getContractFactory('TestIntegers');
|
||||
const integers = await Integers.deploy();
|
||||
await integers.deployed();
|
||||
@ -99,7 +99,7 @@ describe('Get value from storage', function () {
|
||||
expect(value).to.equal(expectedValue);
|
||||
});
|
||||
|
||||
it('get value for integer type variables using single slot', async function () {
|
||||
it('get value for integer type variables using single slot', async () => {
|
||||
const Integers = await ethers.getContractFactory('TestIntegers');
|
||||
const integers = await Integers.deploy();
|
||||
await integers.deployed();
|
||||
@ -112,7 +112,7 @@ describe('Get value from storage', function () {
|
||||
expect(value).to.equal(expectedValue);
|
||||
});
|
||||
|
||||
it('get value for unsigned integer type variables packed together', async function () {
|
||||
it('get value for unsigned integer type variables packed together', async () => {
|
||||
const UnsignedIntegers = await ethers.getContractFactory('TestUnsignedIntegers');
|
||||
const unsignedIntegers = await UnsignedIntegers.deploy();
|
||||
await unsignedIntegers.deployed();
|
||||
@ -131,7 +131,7 @@ describe('Get value from storage', function () {
|
||||
expect(value).to.equal(expectedValue);
|
||||
});
|
||||
|
||||
it('get value for unsigned integer type variables using single slot', async function () {
|
||||
it('get value for unsigned integer type variables using single slot', async () => {
|
||||
const UnsignedIntegers = await ethers.getContractFactory('TestUnsignedIntegers');
|
||||
const unsignedIntegers = await UnsignedIntegers.deploy();
|
||||
await unsignedIntegers.deployed();
|
||||
@ -144,7 +144,7 @@ describe('Get value from storage', function () {
|
||||
expect(value).to.equal(expectedValue);
|
||||
});
|
||||
|
||||
it('get value for boolean type', async function () {
|
||||
it('get value for boolean type', async () => {
|
||||
const Booleans = await ethers.getContractFactory('TestBooleans');
|
||||
const booleans = await Booleans.deploy();
|
||||
await booleans.deployed();
|
||||
@ -163,7 +163,7 @@ describe('Get value from storage', function () {
|
||||
expect(value).to.equal(expectedValue);
|
||||
});
|
||||
|
||||
it('get value for address type', async function () {
|
||||
it('get value for address type', async () => {
|
||||
const Address = await ethers.getContractFactory('TestAddress');
|
||||
const address = await Address.deploy();
|
||||
await address.deployed();
|
||||
@ -177,7 +177,7 @@ describe('Get value from storage', function () {
|
||||
expect(String(value).toLowerCase()).to.equal(signer.address.toLowerCase());
|
||||
});
|
||||
|
||||
it('get value for contract type', async function () {
|
||||
it('get value for contract type', async () => {
|
||||
const contracts = ['TestContractTypes', 'TestAddress'];
|
||||
|
||||
const contractPromises = contracts.map(async (contractName) => {
|
||||
@ -195,7 +195,7 @@ describe('Get value from storage', function () {
|
||||
expect(value).to.equal(testAddress.address.toLowerCase());
|
||||
});
|
||||
|
||||
it('get value for fixed size byte arrays packed together', async function () {
|
||||
it('get value for fixed size byte arrays packed together', async () => {
|
||||
const TestBytes = await ethers.getContractFactory('TestBytes');
|
||||
const testBytes = await TestBytes.deploy();
|
||||
await testBytes.deployed();
|
||||
@ -214,7 +214,7 @@ describe('Get value from storage', function () {
|
||||
expect(value).to.equal(expectedValue);
|
||||
});
|
||||
|
||||
it('get value for fixed size byte arrays using single slot', async function () {
|
||||
it('get value for fixed size byte arrays using single slot', async () => {
|
||||
const TestBytes = await ethers.getContractFactory('TestBytes');
|
||||
const testBytes = await TestBytes.deploy();
|
||||
await testBytes.deployed();
|
||||
@ -227,7 +227,7 @@ describe('Get value from storage', function () {
|
||||
expect(value).to.equal(expectedValue);
|
||||
});
|
||||
|
||||
it('get value for enum types', async function () {
|
||||
it('get value for enum types', async () => {
|
||||
const TestEnums = await ethers.getContractFactory('TestEnums');
|
||||
const testEnums = await TestEnums.deploy();
|
||||
await testEnums.deployed();
|
||||
@ -240,7 +240,7 @@ describe('Get value from storage', function () {
|
||||
expect(value).to.equal(expectedValue);
|
||||
});
|
||||
|
||||
describe('string type', function () {
|
||||
describe('string type', () => {
|
||||
let strings: Contract, storageLayout: StorageLayout;
|
||||
|
||||
before(async () => {
|
||||
@ -250,7 +250,8 @@ describe('Get value from storage', function () {
|
||||
storageLayout = await getStorageLayout('TestStrings');
|
||||
});
|
||||
|
||||
it('get value for string length less than 32 bytes', async function () {
|
||||
// Test for string of size less than 32 bytes which use only one slot.
|
||||
it('get value for string length less than 32 bytes', async () => {
|
||||
const expectedValue = 'Hello world.';
|
||||
await strings.setString1(expectedValue);
|
||||
const blockHash = await getBlockHash();
|
||||
@ -258,7 +259,8 @@ describe('Get value from storage', function () {
|
||||
expect(value).to.equal(expectedValue);
|
||||
});
|
||||
|
||||
it('get value for string length more than 32 bytes', async function () {
|
||||
// Test for string of size 32 bytes or more which use multiple slots.
|
||||
it('get value for string length more than 32 bytes', async () => {
|
||||
const expectedValue = 'This sentence is more than 32 bytes long.';
|
||||
await strings.setString2(expectedValue);
|
||||
const blockHash = await getBlockHash();
|
||||
@ -266,4 +268,45 @@ describe('Get value from storage', function () {
|
||||
expect(value).to.equal(expectedValue);
|
||||
});
|
||||
});
|
||||
|
||||
// Test for array variables which are 32 bytes or less and packed into a single slot.
|
||||
it('get value for fixed size arrays using single slot', async () => {
|
||||
const TestFixedArrays = await ethers.getContractFactory('TestFixedArrays');
|
||||
const testFixedArrays = await TestFixedArrays.deploy();
|
||||
await testFixedArrays.deployed();
|
||||
const storageLayout = await getStorageLayout('TestFixedArrays');
|
||||
|
||||
let expectedValue: Array<number|boolean> = [true, false];
|
||||
|
||||
await testFixedArrays.setBoolArray(expectedValue);
|
||||
let blockHash = await getBlockHash();
|
||||
let { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testFixedArrays.address, 'boolArray');
|
||||
expect(value).to.eql(expectedValue);
|
||||
|
||||
expectedValue = [1, 2, 3, 4, 5];
|
||||
await testFixedArrays.setUint16Array(expectedValue);
|
||||
blockHash = await getBlockHash();
|
||||
({ value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testFixedArrays.address, 'uint16Array'));
|
||||
expect(value).to.eql(expectedValue);
|
||||
});
|
||||
|
||||
// Test for array variables which are more than 32 bytes and use multiple slots.
|
||||
it('get value for fixed size arrays using multiple slots', async () => {
|
||||
const TestFixedArrays = await ethers.getContractFactory('TestFixedArrays');
|
||||
const testFixedArrays = await TestFixedArrays.deploy();
|
||||
await testFixedArrays.deployed();
|
||||
const storageLayout = await getStorageLayout('TestFixedArrays');
|
||||
|
||||
const expectedValue = [1, 2, 3, 4, 5];
|
||||
|
||||
await testFixedArrays.setInt128Array(expectedValue);
|
||||
let blockHash = await getBlockHash();
|
||||
let { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testFixedArrays.address, 'int128Array');
|
||||
expect(value).to.eql(expectedValue);
|
||||
|
||||
await testFixedArrays.setUintArray(expectedValue);
|
||||
blockHash = await getBlockHash();
|
||||
({ value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testFixedArrays.address, 'uintArray'));
|
||||
expect(value).to.eql(expectedValue);
|
||||
});
|
||||
});
|
||||
|
@ -7,19 +7,22 @@ interface Storage {
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface Type {
|
||||
encoding: string;
|
||||
numberOfBytes: string;
|
||||
label: string;
|
||||
interface Types {
|
||||
[type: string]: {
|
||||
encoding: string;
|
||||
numberOfBytes: string;
|
||||
label: string;
|
||||
base?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface StorageLayout {
|
||||
storage: Storage[];
|
||||
types: { [type: string]: Type; }
|
||||
types: Types
|
||||
}
|
||||
|
||||
export interface StorageInfo extends Storage {
|
||||
types: { [type: string]: Type; }
|
||||
types: Types
|
||||
}
|
||||
|
||||
export type GetStorageAt = (param: { blockHash: string, contract: string, slot: string }) => Promise<{ value: string, proof: { data: string } }>
|
||||
@ -56,9 +59,74 @@ export const getStorageInfo = (storageLayout: StorageLayout, variableName: strin
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
export const getStorageValue = async (storageLayout: StorageLayout, getStorageAt: GetStorageAt, blockHash: string, address: string, variableName: string): Promise<{ value: any, proof: { data: string } }> => {
|
||||
const { slot, offset, type, types } = getStorageInfo(storageLayout, variableName);
|
||||
const { encoding, numberOfBytes, label: typeLabel } = types[type];
|
||||
|
||||
return getDecodedValue(getStorageAt, blockHash, address, types, { slot, offset, type });
|
||||
};
|
||||
|
||||
/**
|
||||
* Get value according to type described by the label.
|
||||
* @param storageValue
|
||||
* @param typeLabel
|
||||
*/
|
||||
export const getValueByType = (storageValue: string, typeLabel: string): number | string | boolean => {
|
||||
// Parse value for boolean type.
|
||||
if (typeLabel === 'bool') {
|
||||
return !BigNumber.from(storageValue).isZero();
|
||||
}
|
||||
|
||||
// Parse value for uint/int type or enum type.
|
||||
if (typeLabel.match(/^enum|u?int[0-9]+/)) {
|
||||
return BigNumber.from(storageValue).toNumber();
|
||||
}
|
||||
|
||||
// Parse value for string type.
|
||||
if (typeLabel.includes('string')) {
|
||||
return utils.toUtf8String(storageValue);
|
||||
}
|
||||
|
||||
return storageValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* Function to get decoded value according to type and encoding.
|
||||
* @param getStorageAt
|
||||
* @param blockHash
|
||||
* @param address
|
||||
* @param types
|
||||
* @param storageInfo
|
||||
*/
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
const getDecodedValue = async (getStorageAt: GetStorageAt, blockHash: string, address: string, types: Types, storageInfo: { slot: string, offset: number, type: string }): Promise<{ value: any, proof: { data: string } }> => {
|
||||
const { slot, offset, type } = storageInfo;
|
||||
const { encoding, numberOfBytes, label: typeLabel, base } = types[type];
|
||||
|
||||
const [isArray, arraySize] = typeLabel.match(/\[([0-9]*)\]/) || [false];
|
||||
let value: string, proof: { data: string };
|
||||
|
||||
// If variable is array type.
|
||||
if (isArray && base) {
|
||||
const resultArray = [];
|
||||
const proofs = [];
|
||||
const { numberOfBytes: baseNumberOfBytes } = types[base];
|
||||
|
||||
// TODO: Get values in single call and parse according to type.
|
||||
// Loop over elements of array and get value.
|
||||
for (let i = 0; i < Number(baseNumberOfBytes) * Number(arraySize); i = i + Number(baseNumberOfBytes)) {
|
||||
const arraySlot = BigNumber.from(slot).add(Math.floor(i / 32)).toHexString();
|
||||
const slotOffset = i % 32;
|
||||
const { value, proof } = await getDecodedValue(getStorageAt, blockHash, address, types, { slot: arraySlot, offset: slotOffset, type: base });
|
||||
resultArray.push(value);
|
||||
proofs.push(JSON.parse(proof.data));
|
||||
}
|
||||
|
||||
return {
|
||||
value: resultArray,
|
||||
proof: {
|
||||
data: JSON.stringify(proofs)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Get value according to encoding i.e. how the data is encoded in storage.
|
||||
// https://docs.soliditylang.org/en/v0.8.4/internals/layout_in_storage.html#json-output
|
||||
switch (encoding) {
|
||||
@ -82,31 +150,6 @@ export const getStorageValue = async (storageLayout: StorageLayout, getStorageAt
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get value according to type described by the label.
|
||||
* @param storageValue
|
||||
* @param typeLabel
|
||||
*/
|
||||
// getStorageByType
|
||||
export const getValueByType = (storageValue: string, typeLabel: string): number | string | boolean => {
|
||||
// Parse value for boolean type.
|
||||
if (typeLabel === 'bool') {
|
||||
return !BigNumber.from(storageValue).isZero();
|
||||
}
|
||||
|
||||
// Parse value for uint/int type or enum type.
|
||||
if (typeLabel.match(/^enum|u?int[0-9]+/)) {
|
||||
return BigNumber.from(storageValue).toNumber();
|
||||
}
|
||||
|
||||
// Parse value for string type.
|
||||
if (typeLabel.includes('string')) {
|
||||
return utils.toUtf8String(storageValue);
|
||||
}
|
||||
|
||||
return storageValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* Function to get value for inplace encoding.
|
||||
* @param address
|
||||
@ -160,10 +203,10 @@ const getBytesValue = async (blockHash: string, address: string, slot: string, g
|
||||
}
|
||||
|
||||
// Array to hold multiple bytes32 data.
|
||||
const values = [];
|
||||
const proofs = [
|
||||
JSON.parse(proof.data)
|
||||
];
|
||||
const hexStringArray = [];
|
||||
|
||||
// Compute zero padded hexstring to calculate hashed position of storage.
|
||||
// https://github.com/ethers-io/ethers.js/issues/1079#issuecomment-703056242
|
||||
@ -178,13 +221,13 @@ const getBytesValue = async (blockHash: string, address: string, slot: string, g
|
||||
slot: BigNumber.from(position).add(i).toHexString()
|
||||
});
|
||||
|
||||
values.push(value);
|
||||
hexStringArray.push(value);
|
||||
proofs.push(JSON.parse(proof.data));
|
||||
}
|
||||
|
||||
// Slice trailing bytes according to length of value.
|
||||
return {
|
||||
value: utils.hexDataSlice(utils.hexConcat(values), 0, length),
|
||||
value: utils.hexDataSlice(utils.hexConcat(hexStringArray), 0, length),
|
||||
proof: {
|
||||
data: JSON.stringify(proofs)
|
||||
}
|
||||
|
@ -5,28 +5,33 @@ contract TestFixedArrays {
|
||||
// Fixed size array variable will use 5 consecutive slots as size of 1 element is 32 bytes.
|
||||
uint[5] uintArray;
|
||||
|
||||
// Fixed size array variable will use 10 consecutive slots as size of 1 element is 32 bytes.
|
||||
int[10] intArray;
|
||||
// Fixed size array variable will use 3 slots as size of 1 element is 16 bytes.
|
||||
int128[5] int128Array;
|
||||
|
||||
// Fixed size array variable will use 1 slot as size of one element is 1 byte.
|
||||
int8[2] int8Array;
|
||||
bool[2] boolArray;
|
||||
|
||||
// Fixed size array variable will use the next consecutive slot as it is of array type.
|
||||
// Fixed size array variable will use the next slot as it is of array type.
|
||||
// https://docs.soliditylang.org/en/v0.7.4/internals/layout_in_storage.html#layout-of-state-variables-in-storage
|
||||
uint128[5] uint128Array;
|
||||
uint16[5] uint16Array;
|
||||
|
||||
// Set varaible boolArray.
|
||||
function setBoolArray(bool[2] calldata value) external {
|
||||
boolArray = value;
|
||||
}
|
||||
|
||||
// Set varaible uintArray.
|
||||
function setUintArray(uint[5] calldata value) external {
|
||||
uintArray = value;
|
||||
}
|
||||
|
||||
// Set varaible int8Array.
|
||||
function setInt8Array(int8[2] calldata value) external {
|
||||
int8Array = value;
|
||||
// Set varaible uint16Array.
|
||||
function setUint16Array(uint16[5] calldata value) external {
|
||||
uint16Array = value;
|
||||
}
|
||||
|
||||
// Set varaible uint128Array.
|
||||
function setUint128Array(uint128[5] calldata value) external {
|
||||
uint128Array = value;
|
||||
function setInt128Array(int128[5] calldata value) external {
|
||||
int128Array = value;
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ export const getStorageAt: GetStorageAt = async ({ blockHash, contract, slot })
|
||||
// TODO: Fix use of blockHash as hex string in getStorageAt.
|
||||
// Using blockHash in getStorageAt throws error.
|
||||
// https://github.com/ethers-io/ethers.js/pull/1550#issuecomment-841746994
|
||||
// Using latest tag for temporary fix.
|
||||
// Using latest tag for temporary fix in test scenario.
|
||||
blockHash = 'latest';
|
||||
const value = await ethers.provider.getStorageAt(contract, slot, blockHash);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user