Get value for dynamic arrays (#66)

* Implement getting values of dynamic arrays.

* Add tests for dynamic arrays of value types.

Co-authored-by: nikugogoi <95nikass@gmail.com>
This commit is contained in:
Ashwin Phatak 2021-06-15 18:20:12 +05:30 committed by GitHub
parent 3439dd4041
commit d4db1f5d28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 180 additions and 8 deletions

View File

@ -40,11 +40,11 @@ $ yarn test
* [x] Struct Type
* [ ] Mapping Type
* [ ] Dynamically-sized arrays
* [ ] Integer Type
* [ ] Boolean Type
* [ ] Address Type
* [x] Integer Type
* [x] Boolean Type
* [x] Address Type
* [ ] Fixed-size byte arrays
* [ ] Enum Type
* [x] Enum Type
* [ ] Dynamically-sized byte array
* [ ] Struct Type
* [ ] Mapping Type

View File

@ -417,6 +417,109 @@ describe('Get value from storage', () => {
});
});
describe('dynamic sized arrays', () => {
let testDynamicArrays: Contract, storageLayout: StorageLayout;
before(async () => {
const TestFixedArrays = await ethers.getContractFactory('TestDynamicArrays');
testDynamicArrays = await TestFixedArrays.deploy();
await testDynamicArrays.deployed();
storageLayout = await getStorageLayout('TestDynamicArrays');
});
// Get all elements of array.
it('get value for dynamic sized array of boolean type', async () => {
const boolArray = [true, false, false, true, false];
await testDynamicArrays.setBoolArray(boolArray);
const blockHash = await getBlockHash();
let { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testDynamicArrays.address, 'boolArray');
expect(value).to.eql(boolArray);
const proofData = JSON.parse(proof.data);
expect(proofData.length).to.equal(boolArray.length);
// Get value by index
const arrayIndex = 2;
({ value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testDynamicArrays.address, 'boolArray', arrayIndex));
expect(value).to.equal(boolArray[arrayIndex]);
});
it('get value for dynamic sized array of unsigned integer type', async () => {
const uint128Array = [100, 200, 300, 400, 500];
await testDynamicArrays.setUintArray(uint128Array);
const blockHash = await getBlockHash();
let { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testDynamicArrays.address, 'uintArray');
expect(value).to.eql(uint128Array.map(el => BigInt(el)));
const proofData = JSON.parse(proof.data);
expect(proofData.length).to.equal(uint128Array.length);
// Get value by index
const arrayIndex = 3;
({ value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testDynamicArrays.address, 'uintArray', arrayIndex));
expect(value).to.equal(BigInt(uint128Array[arrayIndex]));
});
it('get value for dynamic sized array of signed integer type', async () => {
const intArray = [10, 20, 30, 40, 50];
await testDynamicArrays.setIntArray(intArray);
const blockHash = await getBlockHash();
let { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testDynamicArrays.address, 'intArray');
expect(value).to.eql(intArray.map(el => BigInt(el)));
const proofData = JSON.parse(proof.data);
expect(proofData.length).to.equal(intArray.length);
// Get value by index
const arrayIndex = 1;
({ value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testDynamicArrays.address, 'intArray', arrayIndex));
expect(value).to.equal(BigInt(intArray[arrayIndex]));
});
it('get value for dynamic sized array of address type', async () => {
const signers = await ethers.getSigners();
const addressArray = signers.map(signer => signer.address.toLowerCase());
await testDynamicArrays.setAddressArray(addressArray);
const blockHash = await getBlockHash();
let { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testDynamicArrays.address, 'addressArray');
expect(value).to.eql(addressArray);
const proofData = JSON.parse(proof.data);
expect(proofData.length).to.equal(addressArray.length);
// Get value by index
const arrayIndex = 4;
({ value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testDynamicArrays.address, 'addressArray', arrayIndex));
expect(value).to.equal(addressArray[arrayIndex]);
});
it.skip('get value for dynamic sized array of fixed size byte array', async () => {
const fixedBytesArray = Array.from({ length: 4 }, () => ethers.utils.hexlify(ethers.utils.randomBytes(10)));
await testDynamicArrays.setFixedBytesArray(fixedBytesArray);
const blockHash = await getBlockHash();
let { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testDynamicArrays.address, 'fixedBytesArray');
expect(value).to.eql(fixedBytesArray);
const proofData = JSON.parse(proof.data);
expect(proofData.length).to.equal(fixedBytesArray.length);
// Get value by index
const arrayIndex = 2;
({ value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testDynamicArrays.address, 'fixedBytesArray', arrayIndex));
expect(value).to.equal(fixedBytesArray[arrayIndex]);
});
it('get value for dynamic sized array of enum type', async () => {
const enumArray = [0, 1, 2, 3];
await testDynamicArrays.setEnumArray(enumArray);
const blockHash = await getBlockHash();
let { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testDynamicArrays.address, 'enumArray');
expect(value).to.eql(enumArray.map(el => BigInt(el)));
const proofData = JSON.parse(proof.data);
expect(proofData.length).to.equal(enumArray.length);
// Get value by index
const arrayIndex = 2;
({ value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testDynamicArrays.address, 'enumArray', arrayIndex));
expect(value).to.equal(BigInt(enumArray[arrayIndex]));
});
});
describe('nested arrays', () => {
let testNestedArrays: Contract, storageLayout: StorageLayout;
const nestedStructArray: Array<Array<{[key: string]: any}>> = [];

View File

@ -106,12 +106,10 @@ const getDecodedValue = async (getStorageAt: GetStorageAt, blockHash: string, ad
const { encoding, numberOfBytes, label: typeLabel, base, value: mappingValueType, key: mappingKeyType, members } = types[type];
let value: string, proof: { data: string };
const arrayMatch = [...typeLabel.matchAll(/\[([0-9]*)\]/g)];
const [isArray, arraySize] = typeLabel.match(/\[([0-9]+)\]$/) || [false];
// If variable is array type.
if (arrayMatch.length && base) {
const arraySize = arrayMatch[arrayMatch.length - 1][1];
if (Boolean(isArray) && base) {
return getArrayValue(getStorageAt, blockHash, address, types, mappingKeys, slot, base, Number(arraySize));
}
@ -147,6 +145,18 @@ const getDecodedValue = async (getStorageAt: GetStorageAt, blockHash: string, ad
break;
}
case 'dynamic_array': {
if (base) {
const { slot: dynamicArraySlot, size } = await getDynamicArrayInfo(getStorageAt, blockHash, address, slot, offset, numberOfBytes);
return getArrayValue(getStorageAt, blockHash, address, types, mappingKeys, dynamicArraySlot, base, size);
} else {
throw new Error('Missing base type for dynamic array.');
}
break;
}
default:
throw new Error(`Encoding ${encoding} not implemented.`);
}
@ -197,6 +207,15 @@ export const getMappingSlot = (types: Types, mappingSlot: string, keyType: strin
return slot;
};
const getDynamicArrayInfo = async (getStorageAt: GetStorageAt, blockHash: string, address: string, slot: string, offset: number, numberOfBytes: string) => {
const { value } = await getInplaceValue(getStorageAt, blockHash, address, slot, offset, numberOfBytes);
const size = Number(getValueByType(value, 'uint'));
const paddedSlot = utils.hexZeroPad(slot, 32);
slot = utils.keccak256(paddedSlot);
return { size, slot };
};
const getArrayValue = async (getStorageAt: GetStorageAt, blockHash: string, address: string, types: Types, mappingKeys: MappingKey[], slot: string, base: string, arraySize: number) => {
const resultArray = [];
const proofs = [];

View File

@ -0,0 +1,50 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;
contract TestDynamicArrays {
// Dynamic sized array variable will use 1 single slot which contains number of array elements.
int[] intArray;
// Dynamic sized array always uses the next consecutive single slot.
uint128[] uintArray;
bool[] boolArray;
address[] addressArray;
bytes10[] fixedBytesArray;
enum Choices { Choice0, Choice1, Choice2, Choice3 }
Choices[] enumArray;
// Set variable intArray.
function setIntArray(int[] calldata value) external {
intArray = value;
}
// Set variable uintArray.
function setUintArray(uint128[] calldata value) external {
uintArray = value;
}
// Set variable boolArray.
function setBoolArray(bool[] calldata value) external {
boolArray = value;
}
// Set variable addressArray.
function setAddressArray(address[] calldata value) external {
addressArray = value;
}
// Set variable fixedBytesArray.
function setFixedBytesArray(bytes10[] calldata value) external {
fixedBytesArray = value;
}
// Set variable enumArray.
function setEnumArray(Choices[] calldata value) external {
enumArray = value;
}
}