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:
Ashwin Phatak
2021-06-03 15:03:39 +05:30
committed by GitHub
co-authored by nikugogoi
parent 00eb129536
commit 23f9a9db41
7 changed files with 198 additions and 77 deletions
+1 -1
View File
@@ -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();
+58 -15
View File
@@ -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);
});
});
+78 -35
View File
@@ -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)
}