diff --git a/packages/solidity-mapper/README.md b/packages/solidity-mapper/README.md index 21a30f5c..cb99a18a 100644 --- a/packages/solidity-mapper/README.md +++ b/packages/solidity-mapper/README.md @@ -29,7 +29,7 @@ $ yarn test * [ ] Reference Types * [ ] Arrays * [x] Get all elements in array - * [ ] Get element in array by index + * [x] Get element in array by index * [ ] Fixed size arrays * [x] Integer Type * [x] Boolean Type diff --git a/packages/solidity-mapper/src/storage.test.ts b/packages/solidity-mapper/src/storage.test.ts index f3903189..720a0f4b 100644 --- a/packages/solidity-mapper/src/storage.test.ts +++ b/packages/solidity-mapper/src/storage.test.ts @@ -271,64 +271,64 @@ describe('Get value from storage', () => { describe('fixed size arrays', () => { let testFixedArrays: Contract, storageLayout: StorageLayout; + const int128Array = [100, 200, 300, 400, 500]; + const uint16Array = [10, 20, 30, 40, 50]; + const boolArray = [true, false]; + let addressArray: string[] = []; before(async () => { const TestFixedArrays = await ethers.getContractFactory('TestFixedArrays'); testFixedArrays = await TestFixedArrays.deploy(); await testFixedArrays.deployed(); storageLayout = await getStorageLayout('TestFixedArrays'); + + const signers = await ethers.getSigners(); + addressArray = signers.map(signer => signer.address.toLowerCase()) + .slice(0, 4); }); + // Get all elements of array. // 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 () => { - let expectedValue: Array = [true, false]; - - await testFixedArrays.setBoolArray(expectedValue); + await testFixedArrays.setBoolArray(boolArray); let blockHash = await getBlockHash(); let { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testFixedArrays.address, 'boolArray'); - expect(value).to.eql(expectedValue); + expect(value).to.eql(boolArray); let proofData = JSON.parse(proof.data); - expect(proofData.length).to.equal(expectedValue.length); + expect(proofData.length).to.equal(boolArray.length); - expectedValue = [1, 2, 3, 4, 5]; - await testFixedArrays.setUint16Array(expectedValue); + await testFixedArrays.setUint16Array(uint16Array); blockHash = await getBlockHash(); ({ value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testFixedArrays.address, 'uint16Array')); - expect(value).to.eql(expectedValue.map(el => BigInt(el))); + expect(value).to.eql(uint16Array.map(el => BigInt(el))); proofData = JSON.parse(proof.data); - expect(proofData.length).to.equal(expectedValue.length); + expect(proofData.length).to.equal(uint16Array.length); }); // 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 expectedValue = [1, 2, 3, 4, 5]; - - await testFixedArrays.setInt128Array(expectedValue); + await testFixedArrays.setInt128Array(int128Array); let blockHash = await getBlockHash(); let { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testFixedArrays.address, 'int128Array'); - expect(value).to.eql(expectedValue.map(el => BigInt(el))); + expect(value).to.eql(int128Array.map(el => BigInt(el))); let proofData = JSON.parse(proof.data); - expect(proofData.length).to.equal(expectedValue.length); + expect(proofData.length).to.equal(int128Array.length); - await testFixedArrays.setUintArray(expectedValue); + await testFixedArrays.setUintArray(uint16Array); blockHash = await getBlockHash(); ({ value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testFixedArrays.address, 'uintArray')); - expect(value).to.eql(expectedValue.map(el => BigInt(el))); + expect(value).to.eql(uint16Array.map(el => BigInt(el))); proofData = JSON.parse(proof.data); - expect(proofData.length).to.equal(expectedValue.length); + expect(proofData.length).to.equal(uint16Array.length); }); it('get value for fixed size arrays of address type', async () => { - const signers = await ethers.getSigners(); - const expectedValue = signers.map(signer => signer.address.toLowerCase()) - .slice(0, 4); - - await testFixedArrays.setAddressArray(expectedValue); + await testFixedArrays.setAddressArray(addressArray); const blockHash = await getBlockHash(); const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testFixedArrays.address, 'addressArray'); - expect(value).to.eql(expectedValue); + expect(value).to.eql(addressArray); const proofData = JSON.parse(proof.data); - expect(proofData.length).to.equal(expectedValue.length); + expect(proofData.length).to.equal(addressArray.length); }); it.skip('get value for fixed size arrays of fixed size bytes type', async () => { @@ -341,6 +341,39 @@ describe('Get value from storage', () => { const proofData = JSON.parse(proof.data); expect(proofData.length).to.equal(expectedValue.length); }); + + // Get element of array by index. + it('get value of signed integer type array by index', async () => { + const arrayIndex = 2; + await testFixedArrays.setInt128Array(int128Array); + const blockHash = await getBlockHash(); + const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testFixedArrays.address, 'int128Array', arrayIndex); + expect(value).to.equal(BigInt(int128Array[arrayIndex])); + }); + + it('get value of unsigned integer type array by index', async () => { + const arrayIndex = 3; + await testFixedArrays.setUint16Array(uint16Array); + const blockHash = await getBlockHash(); + const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testFixedArrays.address, 'uint16Array', arrayIndex); + expect(value).to.equal(BigInt(uint16Array[arrayIndex])); + }); + + it('get value of boolean type array by index', async () => { + const arrayIndex = 0; + await testFixedArrays.setBoolArray(boolArray); + const blockHash = await getBlockHash(); + const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testFixedArrays.address, 'boolArray', arrayIndex); + expect(value).to.equal(boolArray[arrayIndex]); + }); + + it('get value of address type array by index', async () => { + const arrayIndex = 1; + await testFixedArrays.setAddressArray(addressArray); + const blockHash = await getBlockHash(); + const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testFixedArrays.address, 'addressArray', arrayIndex); + expect(value).to.equal(addressArray[arrayIndex]); + }); }); describe('structs type', () => { diff --git a/packages/solidity-mapper/src/storage.ts b/packages/solidity-mapper/src/storage.ts index 82852c47..57504575 100644 --- a/packages/solidity-mapper/src/storage.ts +++ b/packages/solidity-mapper/src/storage.ts @@ -119,12 +119,24 @@ const getDecodedValue = async (getStorageAt: GetStorageAt, blockHash: string, ad baseNumberOfBytes = '32'; } + const getArrayElement = async (index: number, mappingKeys: MappingKey[]) => { + const arrayOffset = index * Number(baseNumberOfBytes); + const arraySlot = BigNumber.from(slot).add(Math.floor(arrayOffset / 32)).toHexString(); + const arraySlotOffset = arrayOffset % 32; + + return getDecodedValue(getStorageAt, blockHash, address, types, { slot: arraySlot, offset: arraySlotOffset, type: base }, mappingKeys); + }; + + const [arrayIndex, ...remainingKeys] = mappingKeys; + + if (typeof arrayIndex === 'number') { + return getArrayElement(arrayIndex, remainingKeys); + } + // 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; - ({ value, proof } = await getDecodedValue(getStorageAt, blockHash, address, types, { slot: arraySlot, offset: slotOffset, type: base }, [])); + for (let i = 0; i < Number(arraySize); i++) { + ({ value, proof } = await getArrayElement(i, mappingKeys)); resultArray.push(value); // Each element in array gets its own proof even if it is packed.