From e3ef17d547c25a803c994dbfa40a34c3dedf367f Mon Sep 17 00:00:00 2001 From: Ashwin Phatak Date: Thu, 17 Jun 2021 11:59:53 +0530 Subject: [PATCH] Fix getting values for arrays. (#73) Co-authored-by: nikugogoi <95nikass@gmail.com> --- packages/solidity-mapper/README.md | 18 +++++++------- packages/solidity-mapper/src/storage.test.ts | 13 ++++++---- packages/solidity-mapper/src/storage.ts | 25 ++++++++++++-------- 3 files changed, 32 insertions(+), 24 deletions(-) diff --git a/packages/solidity-mapper/README.md b/packages/solidity-mapper/README.md index 6e525794..a0060708 100644 --- a/packages/solidity-mapper/README.md +++ b/packages/solidity-mapper/README.md @@ -26,24 +26,24 @@ $ yarn test * [x] Fixed-size byte arrays * [x] Enums * [ ] Function Types -* [ ] Reference Types - * [ ] Arrays +* [x] Reference Types + * [x] Arrays * [x] Get all elements in array * [x] Get element in array by index - * [ ] Fixed size arrays + * [x] Fixed size arrays * [x] Integer Type * [x] Boolean Type * [x] Address Type - * [ ] Fixed-size byte arrays + * [x] Fixed-size byte arrays * [x] Enum type * [x] Dynamically-sized byte array * [x] Struct Type * [x] Mapping Type - * [ ] Dynamically-sized arrays + * [x] Dynamically-sized arrays * [x] Integer Type * [x] Boolean Type * [x] Address Type - * [ ] Fixed-size byte arrays + * [x] Fixed-size byte arrays * [x] Enum Type * [x] Dynamically-sized byte array * [x] Struct Type @@ -54,14 +54,14 @@ $ yarn test * [x] Dynamically-sized byte array * [x] Bytes * [x] String - * [ ] Structs + * [x] Structs * [x] Get struct value with all members * [x] Value Types * [x] Get value of a single member in struct - * [ ] Reference Types + * [x] Reference Types * [x] Struct type members (nested) * [x] Fixed size Array members - * [ ] Dynamically sized Array members + * [x] Dynamically sized Array members * [x] Bytes and string type members * [x] Mapping type members * [ ] Mapping Types diff --git a/packages/solidity-mapper/src/storage.test.ts b/packages/solidity-mapper/src/storage.test.ts index 40524365..326c071c 100644 --- a/packages/solidity-mapper/src/storage.test.ts +++ b/packages/solidity-mapper/src/storage.test.ts @@ -367,7 +367,7 @@ describe('Get value from storage', () => { expect(proofData.length).to.equal(addressArray.length); }); - it.skip('get value for fixed size arrays of fixed size bytes type', async () => { + it('get value for fixed size arrays of fixed size bytes type', async () => { const expectedValue = Array.from({ length: 5 }, () => ethers.utils.hexlify(ethers.utils.randomBytes(10))); await testFixedArrays.setFixedBytesArray(expectedValue); @@ -592,7 +592,7 @@ describe('Get value from storage', () => { expect(value).to.equal(addressArray[arrayIndex]); }); - it.skip('get value for dynamic sized array of fixed size byte array', async () => { + it('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(); @@ -1072,7 +1072,7 @@ describe('Get value from storage', () => { }; dynamicArrayStruct = { - address1: signers[4].address, + address1: signers[4].address.toLowerCase(), uintArray: [1, 2, 3, 4, 5].map(BigInt) }; }); @@ -1099,7 +1099,7 @@ describe('Get value from storage', () => { expect(value).to.eql(stringStruct); }); - it.skip('get value for struct with dynamic array members', async () => { + it('get value for struct with dynamic array members', async () => { await testReferenceStructs.setDynamicArrayStruct(dynamicArrayStruct); const blockHash = await getBlockHash(); const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testReferenceStructs.address, 'dynamicArrayStruct'); @@ -1138,7 +1138,7 @@ describe('Get value from storage', () => { expect(value).to.eql(stringStruct[member]); }); - it.skip('get value of dynamic array member in a struct', async () => { + it('get value of dynamic array member in a struct', async () => { const member = 'uintArray'; const blockHash = await getBlockHash(); const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testReferenceStructs.address, 'dynamicArrayStruct', member); @@ -1247,6 +1247,9 @@ describe('Get value from storage', () => { expect(value).to.equal(expectedValue); }); + // TODO: Fix getting value for mapping with keys as fixed-size byte array + // Zero value is returned if using fixed-sized byte array keys of length less than 32 bytes + // Type Bytes32 works whereas types like bytes16, bytes24 do not work. it.skip('get value for mapping with fixed-size byte array keys', async () => { const mapKey = ethers.utils.hexlify(ethers.utils.randomBytes(8)); const [, signer1] = await ethers.getSigners(); diff --git a/packages/solidity-mapper/src/storage.ts b/packages/solidity-mapper/src/storage.ts index d04e54be..a0c17b12 100644 --- a/packages/solidity-mapper/src/storage.ts +++ b/packages/solidity-mapper/src/storage.ts @@ -219,17 +219,22 @@ const getDynamicArrayInfo = async (getStorageAt: GetStorageAt, blockHash: string const getArrayValue = async (getStorageAt: GetStorageAt, blockHash: string, address: string, types: Types, mappingKeys: MappingKey[], slot: string, base: string, arraySize: number) => { const resultArray = []; const proofs = []; - let { numberOfBytes: baseNumberOfBytes, label: baseTypeLabel } = types[base]; - - // Address type elements use an entire single slot i.e. 32 bytes. - if (baseTypeLabel === 'address' || baseTypeLabel.includes('contract')) { - baseNumberOfBytes = '32'; - } + const { numberOfBytes: baseNumberOfBytes } = types[base]; const getArrayElement = async (mappingKeys: MappingKey[], index: number) => { - const arrayOffset = index * Number(baseNumberOfBytes); - const arraySlot = BigNumber.from(slot).add(Math.floor(arrayOffset / 32)).toHexString(); - const arraySlotOffset = arrayOffset % 32; + let arraySlotOffset = 0; + let slotIndex; + + if (Number(baseNumberOfBytes) <= 32) { + const elementsInSlot = Math.floor(32 / Number(baseNumberOfBytes)); + slotIndex = Math.floor(index / elementsInSlot); + arraySlotOffset = (index % elementsInSlot) * Number(baseNumberOfBytes); + } else { + const slotsUsedByElement = Math.ceil(Number(baseNumberOfBytes) / 32); + slotIndex = slotsUsedByElement * index; + } + + const arraySlot = BigNumber.from(slot).add(slotIndex).toHexString(); return getDecodedValue(getStorageAt, blockHash, address, types, { slot: arraySlot, offset: arraySlotOffset, type: base }, mappingKeys); }; @@ -240,7 +245,6 @@ const getArrayValue = async (getStorageAt: GetStorageAt, blockHash: string, addr return getArrayElement(remainingKeys, arrayIndex); } - // TODO: Get values in single call and parse according to type. // Loop over elements of array and get value. for (let i = 0; i < arraySize; i++) { const { value, proof } = await getArrayElement(mappingKeys, i); @@ -309,6 +313,7 @@ const getStructureValue = async (getStorageAt: GetStorageAt, blockHash: string, * @param getStorageAt */ const getInplaceValue = async (getStorageAt: GetStorageAt, blockHash: string, address: string, slot: string, offset: number, numberOfBytes: string) => { + // TODO: Memoize getStorageAt function for duplicate multiple calls. const { value, proof } = await getStorageAt({ blockHash, contract: address, slot }); const valueLength = utils.hexDataLength(value);