diff --git a/packages/solidity-mapper/README.md b/packages/solidity-mapper/README.md index 53075fd5..7d0abc99 100644 --- a/packages/solidity-mapper/README.md +++ b/packages/solidity-mapper/README.md @@ -37,7 +37,7 @@ $ yarn test * [ ] Fixed-size byte arrays * [ ] Enum type * [ ] Dynamically-sized byte array - * [ ] Struct Type + * [x] Struct Type * [ ] Mapping Type * [ ] Dynamically-sized arrays * [ ] Integer Type @@ -64,6 +64,9 @@ $ yarn test * [ ] Fixed-size byte array keys * [x] Dynamically-sized byte array keys * [ ] Reference Type Mapping values + * [x] Struct type values + * [ ] Array type values + * [ ] Dynamically sized Bytes and string type values * [x] Nested Mapping ## Observations diff --git a/packages/solidity-mapper/hardhat.config.ts b/packages/solidity-mapper/hardhat.config.ts index 60f2170d..e8559256 100644 --- a/packages/solidity-mapper/hardhat.config.ts +++ b/packages/solidity-mapper/hardhat.config.ts @@ -16,7 +16,7 @@ task('accounts', 'Prints the list of accounts', async (args, hre) => { const config: HardhatUserConfig = { solidity: { - version: '0.7.3', + version: '0.7.6', settings: { outputSelection: { '*': { diff --git a/packages/solidity-mapper/src/storage.test.ts b/packages/solidity-mapper/src/storage.test.ts index 74824cd3..abcec1b1 100644 --- a/packages/solidity-mapper/src/storage.test.ts +++ b/packages/solidity-mapper/src/storage.test.ts @@ -342,6 +342,27 @@ describe('Get value from storage', () => { expect(proofData.length).to.equal(expectedValue.length); }); + it('get value for fixed size array of struct type', async () => { + const expectedValue = []; + + for (let i = 0; i < 5; i++) { + const structElement = { + int1: BigInt(i + 1), + uint1: BigInt(i + 2), + bool1: Boolean(i % 2) + }; + + expectedValue[i] = structElement; + await testFixedArrays.setStructArray(structElement, i); + } + + const blockHash = await getBlockHash(); + const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testFixedArrays.address, 'structArray'); + expect(value).to.eql(expectedValue); + 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; @@ -374,6 +395,25 @@ describe('Get value from storage', () => { const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testFixedArrays.address, 'addressArray', arrayIndex); expect(value).to.equal(addressArray[arrayIndex]); }); + + it('get value of struct type array by index', async () => { + const expectedValue = { + int1: BigInt(123), + uint1: BigInt(456), + bool1: false + }; + + const arrayIndex = 2; + await testFixedArrays.setStructArray(expectedValue, arrayIndex); + const blockHash = await getBlockHash(); + let { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testFixedArrays.address, 'structArray', arrayIndex); + expect(value).to.eql(expectedValue); + + // Get value of specified struct member in array element. + const structMember = 'uint1'; + ({ value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testFixedArrays.address, 'structArray', arrayIndex, structMember)); + expect(value).to.eql(expectedValue[structMember]); + }); }); describe('structs with value type members', () => { @@ -381,7 +421,12 @@ describe('Get value from storage', () => { /* eslint-disable @typescript-eslint/no-explicit-any */ let addressStruct: { [key: string]: any }, contractStruct: { [key: string]: any }; - const multipleSlotStruct = { + const singleSlotStruct = { + int1: BigInt(123), + uint1: BigInt(4) + }; + + const multipleSlotStruct: { [key: string]: any } = { uint1: BigInt(123), bool1: false, int1: BigInt(456) @@ -426,15 +471,10 @@ describe('Get value from storage', () => { // Get all members of a struct. it('get value for struct using a single slot', async () => { - const expectedValue = { - int1: BigInt(123), - uint1: BigInt(4) - }; - - await testValueStructs.setSingleSlotStruct(expectedValue.int1, expectedValue.uint1); + await testValueStructs.setSingleSlotStruct(singleSlotStruct.int1, singleSlotStruct.uint1); const blockHash = await getBlockHash(); const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testValueStructs.address, 'singleSlotStruct'); - expect(value).to.eql(expectedValue); + expect(value).to.eql(singleSlotStruct); const proofData = JSON.parse(proof.data); expect(proofData).to.have.all.keys('int1', 'uint1'); }); @@ -449,7 +489,7 @@ describe('Get value from storage', () => { }); it('get value for struct with address type members', async () => { - await testValueStructs.setAddressStruct(addressStruct.int1, addressStruct.address1, addressStruct.address2, addressStruct.uint1); + await testValueStructs.setAddressStruct(addressStruct); const blockHash = await getBlockHash(); const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testValueStructs.address, 'addressStruct'); expect(value).to.eql(addressStruct); @@ -487,31 +527,45 @@ describe('Get value from storage', () => { // Get value of a member in a struct it('get value of signed integer type member in a struct', async () => { const member = 'int1'; - await testValueStructs.setMultipleSlotStruct(multipleSlotStruct.uint1, multipleSlotStruct.bool1, multipleSlotStruct.int1); + await testValueStructs.setSingleSlotStruct(singleSlotStruct.int1, singleSlotStruct.uint1); const blockHash = await getBlockHash(); - const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testValueStructs.address, 'multipleSlotStruct', member); - expect(value).to.equal(multipleSlotStruct[member]); + const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testValueStructs.address, 'singleSlotStruct', member); + expect(value).to.equal(singleSlotStruct[member]); }); it('get value of unsigned integer type member in a struct', async () => { const member = 'uint1'; const blockHash = await getBlockHash(); - const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testValueStructs.address, 'multipleSlotStruct', member); - expect(value).to.equal(multipleSlotStruct[member]); + const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testValueStructs.address, 'singleSlotStruct', member); + expect(value).to.equal(singleSlotStruct[member]); }); it('get value of boolean type member in a struct', async () => { - const member = 'bool1'; + await testValueStructs.setMultipleSlotStruct(multipleSlotStruct.uint1, multipleSlotStruct.bool1, multipleSlotStruct.int1); const blockHash = await getBlockHash(); - const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testValueStructs.address, 'multipleSlotStruct', member); + + let member = 'bool1'; + let { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testValueStructs.address, 'multipleSlotStruct', member); + expect(value).to.equal(multipleSlotStruct[member]); + + member = 'int1'; + ({ value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testValueStructs.address, 'multipleSlotStruct', member)); + expect(value).to.equal(multipleSlotStruct[member]); + + member = 'uint1'; + ({ value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testValueStructs.address, 'multipleSlotStruct', member)); expect(value).to.equal(multipleSlotStruct[member]); }); it('get value of address type member in a struct', async () => { - const member = 'address1'; - await testValueStructs.setAddressStruct(addressStruct.int1, addressStruct.address1, addressStruct.address2, addressStruct.uint1); + await testValueStructs.setAddressStruct(addressStruct); const blockHash = await getBlockHash(); - const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testValueStructs.address, 'addressStruct', member); + let member = 'address1'; + let { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testValueStructs.address, 'addressStruct', member); + expect(value).to.equal(addressStruct[member]); + + member = 'uint1'; + ({ value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testValueStructs.address, 'addressStruct', member)); expect(value).to.equal(addressStruct[member]); }); @@ -644,6 +698,7 @@ describe('Get value from storage', () => { storageLayout = await getStorageLayout('TestBasicMapping'); }); + // Tests for value type keys. it('get value for mapping with address type keys', async () => { const expectedValue = 123; const [, signer1] = await ethers.getSigners(); @@ -698,6 +753,7 @@ describe('Get value from storage', () => { expect(value).to.equal(BigInt(expectedValue)); }); + // Tests for reference type keys. it('get value for mapping with string type keys', async () => { const mapKey = 'abc'; const expectedValue = 123; @@ -715,6 +771,45 @@ describe('Get value from storage', () => { const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testMappingTypes.address, 'bytesUintMap', mapKey); expect(value).to.equal(BigInt(expectedValue)); }); + + // Tests for reference type values. + it('get value for mapping with struct type values', async () => { + const expectedValue = { + uint1: BigInt(123), + int1: BigInt(456), + bool1: true + }; + + const mapKey = 123; + await testMappingTypes.setIntStructMap(mapKey, expectedValue); + const blockHash = await getBlockHash(); + let { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testMappingTypes.address, 'intStructMap', mapKey); + expect(value).to.eql(expectedValue); + + // Get value of specified struct member in mapping. + const structMember = 'bool1'; + ({ value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testMappingTypes.address, 'intStructMap', mapKey, structMember)); + expect(value).to.equal(expectedValue[structMember]); + }); + + it('get value for mapping of fixed size bytes keys and struct type values', async () => { + const expectedValue = { + uint1: BigInt(123), + int1: BigInt(456), + bool1: true + }; + + const mapKey = ethers.utils.hexlify(ethers.utils.randomBytes(32)); + await testMappingTypes.setFixedBytesStructMap(mapKey, expectedValue); + const blockHash = await getBlockHash(); + let { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testMappingTypes.address, 'fixedBytesStructMap', mapKey); + expect(value).to.eql(expectedValue); + + // Get value of specified struct member in mapping. + const structMember = 'int1'; + ({ value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testMappingTypes.address, 'fixedBytesStructMap', mapKey, structMember)); + expect(value).to.equal(expectedValue[structMember]); + }); }); describe('nested mapping type', () => { @@ -775,5 +870,14 @@ describe('Get value from storage', () => { const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testNestedMapping.address, 'stringAddressIntMap', key, signer1.address); expect(value).to.equal(BigInt(expectedValue)); }); + + it('get value for double nested mapping with address type keys', async () => { + const [signer1, signer2, signer3] = await ethers.getSigners(); + const uintKey = 123; + await testNestedMapping.setDoubleNestedAddressMap(signer1.address, signer2.address, uintKey, signer3.address); + const blockHash = await getBlockHash(); + const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testNestedMapping.address, 'doubleNestedAddressMap', signer1.address, signer2.address, uintKey); + expect(value).to.equal(signer3.address.toLowerCase()); + }); }); }); diff --git a/packages/solidity-mapper/test/contracts/TestBasicMapping.sol b/packages/solidity-mapper/test/contracts/TestBasicMapping.sol index 13764861..c506fb1c 100644 --- a/packages/solidity-mapper/test/contracts/TestBasicMapping.sol +++ b/packages/solidity-mapper/test/contracts/TestBasicMapping.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.7.0; +pragma solidity ^0.7.6; +pragma abicoder v2; contract TestBasicMapping { // Mapping type variable occupies one single slot but the actual elements are stored at a different storage slot that is computed using a Keccak-256 hash. @@ -30,6 +31,18 @@ contract TestBasicMapping { // Mapping with dynamically-sized byte array as keys and unsigned integer type values. mapping(bytes => uint) public bytesUintMap; + struct TestStruct { + uint128 uint1; + int56 int1; + bool bool1; + } + + // Mapping with signed integer as keys and struct type values. + mapping(int24 => TestStruct) public intStructMap; + + // Mapping with signed integer as keys and struct type values. + mapping(bytes32 => TestStruct) public fixedBytesStructMap; + // Set variable addressUintMap. function setAddressUintMap(uint value) external { addressUintMap[msg.sender] = value; @@ -69,4 +82,14 @@ contract TestBasicMapping { function setBytesUintMap(bytes calldata key, uint value) external { bytesUintMap[key] = value; } + + // Set variable intStruct. + function setIntStructMap(int24 key, TestStruct calldata value) external { + intStructMap[key] = value; + } + + // Set variable fixedBytesStructMap. + function setFixedBytesStructMap(bytes32 key, TestStruct calldata value) external { + fixedBytesStructMap[key] = value; + } } diff --git a/packages/solidity-mapper/test/contracts/TestFixedArrays.sol b/packages/solidity-mapper/test/contracts/TestFixedArrays.sol index 98175b82..bf362443 100644 --- a/packages/solidity-mapper/test/contracts/TestFixedArrays.sol +++ b/packages/solidity-mapper/test/contracts/TestFixedArrays.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.7.0; +pragma solidity ^0.7.6; +pragma abicoder v2; contract TestFixedArrays { // Fixed size array variable will use 5 consecutive slots as size of 1 element is 32 bytes. @@ -19,6 +20,14 @@ contract TestFixedArrays { bytes10[5] bytesArray; + struct TestStruct { + uint32 uint1; + int56 int1; + bool bool1; + } + + TestStruct[5] structArray; + // Set variable boolArray. function setBoolArray(bool[2] calldata value) external { boolArray = value; @@ -48,4 +57,9 @@ contract TestFixedArrays { function setBytesArray(bytes10[5] calldata value) external { bytesArray = value; } + + // Set variable structArray. + function setStructArray(TestStruct calldata value, uint index) external { + structArray[index] = value; + } } diff --git a/packages/solidity-mapper/test/contracts/TestNestedMapping.sol b/packages/solidity-mapper/test/contracts/TestNestedMapping.sol index 1b9062ef..1555c01b 100644 --- a/packages/solidity-mapper/test/contracts/TestNestedMapping.sol +++ b/packages/solidity-mapper/test/contracts/TestNestedMapping.sol @@ -14,6 +14,8 @@ contract TestNestedMapping { mapping (string => mapping (address => int)) private stringAddressIntMap; + mapping (address => mapping (address => mapping (uint24 => address))) public doubleNestedAddressMap; + // Set variable nestedAddressUintMap. function setNestedAddressUintMap(address nestedKey, uint value) external { nestedAddressUintMap[msg.sender][nestedKey] = value; @@ -38,4 +40,9 @@ contract TestNestedMapping { function setStringAddressIntMap(string calldata key, address nestedKey, int value) external { stringAddressIntMap[key][nestedKey] = value; } + + // Set variable doubleNestedAddressMap. + function setDoubleNestedAddressMap(address key, address nestedKey, uint24 doubleNestedKey, address value) external { + doubleNestedAddressMap[key][nestedKey][doubleNestedKey] = value; + } } diff --git a/packages/solidity-mapper/test/contracts/TestValueStructs.sol b/packages/solidity-mapper/test/contracts/TestValueStructs.sol index 0a8982ef..52f4fd1d 100644 --- a/packages/solidity-mapper/test/contracts/TestValueStructs.sol +++ b/packages/solidity-mapper/test/contracts/TestValueStructs.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.7.0; +pragma solidity ^0.7.6; +pragma abicoder v2; import "./TestContractTypes.sol"; @@ -69,11 +70,8 @@ contract TestValueStructs { } // Set variable addressStruct. - function setAddressStruct(int8 int1Value, address address1Value, address address2Value, uint16 uint1Value) external { - addressStruct.int1 = int1Value; - addressStruct.address1 = address1Value; - addressStruct.address2 = address2Value; - addressStruct.uint1 = uint1Value; + function setAddressStruct(AddressStruct calldata value) external { + addressStruct = value; } // Set variable contractStruct.