From b243025ca8869cb0c9e7e5d65b7b68fff4604eae Mon Sep 17 00:00:00 2001 From: Ashwin Phatak Date: Wed, 2 Jun 2021 11:23:33 +0530 Subject: [PATCH] Test cases in solidity-mapper for contract, fixed-size byte arrays and enum types (#26) * Add tests for getStorageInfo and getEventNameTopics. * Lint solidity-mapper package code. * Add test for contract type. * Add test for fixed size byte arrays. * Add test for Enum types. * Add tests for variables packed together and using single slot. * Fix comments in test contracts. Co-authored-by: nikugogoi <95nikass@gmail.com> --- packages/solidity-mapper/README.md | 9 +- packages/solidity-mapper/hardhat.config.ts | 24 +- packages/solidity-mapper/src/logs.test.ts | 33 +++ packages/solidity-mapper/src/logs.ts | 14 +- packages/solidity-mapper/src/storage.test.ts | 254 ++++++++++++++---- packages/solidity-mapper/src/storage.ts | 33 ++- .../test/contracts/TestAddress.sol | 2 + .../test/contracts/TestBooleans.sol | 2 +- .../test/contracts/TestBytes.sol | 37 +++ .../test/contracts/TestContractTypes.sol | 22 ++ .../test/contracts/TestEnums.sol | 16 ++ .../test/contracts/TestEvents.sol | 11 + .../test/contracts/TestFixedArrays.sol | 32 +++ .../test/contracts/TestIntegers.sol | 6 +- .../test/contracts/TestStrings.sol | 4 + .../test/contracts/TestUnsignedIntegers.sol | 6 +- packages/solidity-mapper/test/utils.ts | 17 +- 17 files changed, 420 insertions(+), 102 deletions(-) create mode 100644 packages/solidity-mapper/src/logs.test.ts create mode 100644 packages/solidity-mapper/test/contracts/TestBytes.sol create mode 100644 packages/solidity-mapper/test/contracts/TestContractTypes.sol create mode 100644 packages/solidity-mapper/test/contracts/TestEnums.sol create mode 100644 packages/solidity-mapper/test/contracts/TestEvents.sol create mode 100644 packages/solidity-mapper/test/contracts/TestFixedArrays.sol diff --git a/packages/solidity-mapper/README.md b/packages/solidity-mapper/README.md index 0231c1ad..7e83edb7 100644 --- a/packages/solidity-mapper/README.md +++ b/packages/solidity-mapper/README.md @@ -21,11 +21,14 @@ $ yarn test * [x] Integers * [ ] Fixed Point Numbers * [x] Address -* [ ] Contract Types -* [ ] Fixed-size byte arrays -* [ ] Enums +* [x] Contract Types +* [x] Fixed-size byte arrays +* [x] Enums * [ ] Function Types * [ ] Arrays +* [ ] Dynamically-sized byte array + * [ ] Bytes + * [x] String * [ ] Structs * [ ] Mapping Types diff --git a/packages/solidity-mapper/hardhat.config.ts b/packages/solidity-mapper/hardhat.config.ts index 3cf1d0c9..60f2170d 100644 --- a/packages/solidity-mapper/hardhat.config.ts +++ b/packages/solidity-mapper/hardhat.config.ts @@ -1,9 +1,9 @@ -import { task, HardhatUserConfig } from "hardhat/config"; -import "@nomiclabs/hardhat-waffle"; +import { task, HardhatUserConfig } from 'hardhat/config'; +import '@nomiclabs/hardhat-waffle'; // This is a sample Hardhat task. To learn how to create your own go to // https://hardhat.org/guides/create-task.html -task("accounts", "Prints the list of accounts", async (args, hre) => { +task('accounts', 'Prints the list of accounts', async (args, hre) => { const accounts = await hre.ethers.getSigners(); for (const account of accounts) { @@ -16,20 +16,20 @@ task("accounts", "Prints the list of accounts", async (args, hre) => { const config: HardhatUserConfig = { solidity: { - version: "0.7.3", + version: '0.7.3', settings: { outputSelection: { - "*": { - "*": [ - "abi", "storageLayout", - "metadata", "evm.bytecode", // Enable the metadata and bytecode outputs of every single contract. - "evm.bytecode.sourceMap" // Enable the source map output of every single contract. + '*': { + '*': [ + 'abi', 'storageLayout', + 'metadata', 'evm.bytecode', // Enable the metadata and bytecode outputs of every single contract. + 'evm.bytecode.sourceMap' // Enable the source map output of every single contract. ], - "": [ - "ast" // Enable the AST output of every single file. + '': [ + 'ast' // Enable the AST output of every single file. ] } - }, + } } }, paths: { diff --git a/packages/solidity-mapper/src/logs.test.ts b/packages/solidity-mapper/src/logs.test.ts new file mode 100644 index 00000000..94a78122 --- /dev/null +++ b/packages/solidity-mapper/src/logs.test.ts @@ -0,0 +1,33 @@ +import { expect } from 'chai'; +import { artifacts, ethers } from 'hardhat'; + +import { getEventNameTopics } from './logs'; + +const TEST_DATA = [ + { + name: 'TestIntegers', + output: {} + }, + { + name: 'TestEvents', + output: { + // Signature of event is a keccak256 hash of event name and input argument types. + // keccak256('Event1(string,string)') + Event1: '0xead5fc99a8133dbf3f4e87d1ada4e5a4cf65170fad6445d34042e643f6a30b79' + } + } +]; + +it('get event name topics', async function () { + const testPromises = TEST_DATA.map(async ({ name, output }) => { + const Contract = await ethers.getContractFactory(name); + const contract = await Contract.deploy(); + await contract.deployed(); + const { abi } = await artifacts.readArtifact(name); + + const eventNameTopics = getEventNameTopics(abi); + expect(eventNameTopics).to.eql(output); + }); + + await Promise.all(testPromises); +}); diff --git a/packages/solidity-mapper/src/logs.ts b/packages/solidity-mapper/src/logs.ts index 2e95dc8d..428935b4 100644 --- a/packages/solidity-mapper/src/logs.ts +++ b/packages/solidity-mapper/src/logs.ts @@ -1,7 +1,7 @@ -import { JsonFragment } from "@ethersproject/abi" -import { utils } from "ethers"; +import { JsonFragment } from '@ethersproject/abi'; +import { utils } from 'ethers'; -interface EventNameTopic { +interface EventNameTopics { [eventName: string]: string } @@ -9,10 +9,10 @@ interface EventNameTopic { * Function to get event name topics from abi. * @param abi */ -export const getEventNameTopics = (abi: JsonFragment[]): EventNameTopic => { +export const getEventNameTopics = (abi: JsonFragment[]): EventNameTopics => { const eventFragments = abi.filter(({ type }) => type === 'event'); - return eventFragments.reduce((acc: EventNameTopic, { name, inputs }) => { + return eventFragments.reduce((acc: EventNameTopics, { name, inputs }) => { if (inputs && name) { const inputParamsString = inputs.map(({ type }) => type) .join(','); @@ -22,5 +22,5 @@ export const getEventNameTopics = (abi: JsonFragment[]): EventNameTopic => { } return acc; - }, {}) -} + }, {}); +}; diff --git a/packages/solidity-mapper/src/storage.test.ts b/packages/solidity-mapper/src/storage.test.ts index 786480ea..c9dd16e9 100644 --- a/packages/solidity-mapper/src/storage.test.ts +++ b/packages/solidity-mapper/src/storage.test.ts @@ -1,89 +1,247 @@ -import { Contract } from "@ethersproject/contracts"; -import { expect } from "chai"; -import hre from "hardhat"; -import "@nomiclabs/hardhat-ethers"; +import { Contract } from '@ethersproject/contracts'; +import { expect } from 'chai'; +import { ethers } from 'hardhat'; +import '@nomiclabs/hardhat-ethers'; -import { getStorageValue, StorageLayout } from "./storage"; -import { getStorageLayout, getStorageAt } from "../test/utils"; +import { getStorageInfo, getStorageValue, StorageLayout } from './storage'; +import { getStorageLayout, getStorageAt } from '../test/utils'; -describe("Storage", function() { - it("get value for integer type", async function() { - const Integers = await hre.ethers.getContractFactory("TestIntegers"); +const TEST_DATA = [ + { + name: 'TestBooleans', + variable: 'bool1', + output: { + label: 'bool1', + offset: 0, + slot: '0x00', + type: 't_bool' + } + }, + { + name: 'TestIntegers', + variable: 'int2', + output: { + slot: '0x00', + offset: 1, + type: 't_int16', + label: 'int2' + } + }, + { + name: 'TestUnsignedIntegers', + variable: 'uint3', + output: { + label: 'uint3', + offset: 0, + slot: '0x01', + type: 't_uint256' + } + }, + { + name: 'TestAddress', + variable: 'address1', + output: { + label: 'address1', + offset: 0, + slot: '0x00', + type: 't_address' + } + }, + { + name: 'TestStrings', + variable: 'string2', + output: { + label: 'string2', + offset: 0, + slot: '0x01', + type: 't_string_storage' + } + } +]; + +it('get storage information', async function () { + const testPromises = TEST_DATA.map(async ({ name, variable, output }) => { + const Contract = await ethers.getContractFactory(name); + const contract = await Contract.deploy(); + await contract.deployed(); + const storageLayout = await getStorageLayout(name); + + const storageInfo = getStorageInfo(storageLayout, variable); + expect(storageInfo).to.include(output); + }); + + await Promise.all(testPromises); +}); + +describe('Get value from storage', function () { + it('get value for integer type variables packed together', async function () { + const Integers = await ethers.getContractFactory('TestIntegers'); const integers = await Integers.deploy(); await integers.deployed(); - const storageLayout = await getStorageLayout("TestIntegers"); + const storageLayout = await getStorageLayout('TestIntegers'); - // if (storageLayout) let value = 12; await integers.setInt1(value); - let storageValue = await getStorageValue(integers.address, storageLayout, getStorageAt, "int1"); + let storageValue = await getStorageValue(integers.address, storageLayout, getStorageAt, 'int1'); + expect(storageValue).to.equal(value); + + value = 34; + await integers.setInt2(value); + storageValue = await getStorageValue(integers.address, storageLayout, getStorageAt, 'int2'); expect(storageValue).to.equal(value); }); - it("get value for unsigned integer type", async function() { - const UnsignedIntegers = await hre.ethers.getContractFactory("TestUnsignedIntegers"); - const unsignedIntegers = await UnsignedIntegers.deploy(); - await unsignedIntegers.deployed(); - const storageLayout = await getStorageLayout("TestUnsignedIntegers"); + it('get value for integer type variables using single slot', async function () { + const Integers = await ethers.getContractFactory('TestIntegers'); + const integers = await Integers.deploy(); + await integers.deployed(); + const storageLayout = await getStorageLayout('TestIntegers'); const value = 123; - await unsignedIntegers.setUint1(value); - const storageValue = await getStorageValue(unsignedIntegers.address, storageLayout, getStorageAt, "uint1"); + await integers.setInt3(value); + const storageValue = await getStorageValue(integers.address, storageLayout, getStorageAt, 'int3'); expect(storageValue).to.equal(value); }); - it("get value for boolean type", async function() { - const Booleans = await hre.ethers.getContractFactory("TestBooleans"); - const booleans = await Booleans.deploy(); - await booleans.deployed(); - const storageLayout = await getStorageLayout("TestBooleans"); + it('get value for unsigned integer type variables packed together', async function () { + const UnsignedIntegers = await ethers.getContractFactory('TestUnsignedIntegers'); + const unsignedIntegers = await UnsignedIntegers.deploy(); + await unsignedIntegers.deployed(); + const storageLayout = await getStorageLayout('TestUnsignedIntegers'); - let value = true - await booleans.setBool1(value); - let storageValue = await getStorageValue(booleans.address, storageLayout, getStorageAt, "bool1"); - expect(storageValue).to.equal(value) + let value = 12; + await unsignedIntegers.setUint1(value); + let storageValue = await getStorageValue(unsignedIntegers.address, storageLayout, getStorageAt, 'uint1'); + expect(storageValue).to.equal(value); - value = false - await booleans.setBool2(value); - storageValue = await getStorageValue(booleans.address, storageLayout, getStorageAt, "bool2") - expect(storageValue).to.equal(value) + value = 34; + await unsignedIntegers.setUint2(value); + storageValue = await getStorageValue(unsignedIntegers.address, storageLayout, getStorageAt, 'uint2'); + expect(storageValue).to.equal(value); }); - it("get value for address type", async function() { - const Address = await hre.ethers.getContractFactory("TestAddress"); + it('get value for unsigned integer type variables using single slot', async function () { + const UnsignedIntegers = await ethers.getContractFactory('TestUnsignedIntegers'); + const unsignedIntegers = await UnsignedIntegers.deploy(); + await unsignedIntegers.deployed(); + const storageLayout = await getStorageLayout('TestUnsignedIntegers'); + + const value = 123; + await unsignedIntegers.setUint3(value); + const storageValue = await getStorageValue(unsignedIntegers.address, storageLayout, getStorageAt, 'uint3'); + expect(storageValue).to.equal(value); + }); + + it('get value for boolean type', async function () { + const Booleans = await ethers.getContractFactory('TestBooleans'); + const booleans = await Booleans.deploy(); + await booleans.deployed(); + const storageLayout = await getStorageLayout('TestBooleans'); + + let value = true; + await booleans.setBool1(value); + let storageValue = await getStorageValue(booleans.address, storageLayout, getStorageAt, 'bool1'); + expect(storageValue).to.equal(value); + + value = false; + await booleans.setBool2(value); + storageValue = await getStorageValue(booleans.address, storageLayout, getStorageAt, 'bool2'); + expect(storageValue).to.equal(value); + }); + + it('get value for address type', async function () { + const Address = await ethers.getContractFactory('TestAddress'); const address = await Address.deploy(); await address.deployed(); - const storageLayout = await getStorageLayout("TestAddress"); + const storageLayout = await getStorageLayout('TestAddress'); - const [signer] = await hre.ethers.getSigners(); + const [signer] = await ethers.getSigners(); await address.setAddress1(signer.address); - const storageValue = await getStorageValue(address.address, storageLayout, getStorageAt, "address1"); + const storageValue = await getStorageValue(address.address, storageLayout, getStorageAt, 'address1'); expect(storageValue).to.be.a('string'); expect(String(storageValue).toLowerCase()).to.equal(signer.address.toLowerCase()); }); - describe("string type", function () { + it('get value for contract type', async function () { + const contracts = ['TestContractTypes', 'TestAddress']; + + const contractPromises = contracts.map(async (contractName) => { + const Contract = await ethers.getContractFactory(contractName); + const contract = await Contract.deploy(); + return contract.deployed(); + }); + + const [testContractTypes, testAddress] = await Promise.all(contractPromises); + const storageLayout = await getStorageLayout('TestContractTypes'); + + await testContractTypes.setAddressContract1(testAddress.address); + const storageValue = await getStorageValue(testContractTypes.address, storageLayout, getStorageAt, 'addressContract1'); + expect(storageValue).to.equal(testAddress.address.toLowerCase()); + }); + + it('get value for fixed size byte arrays packed together', async function () { + const TestBytes = await ethers.getContractFactory('TestBytes'); + const testBytes = await TestBytes.deploy(); + await testBytes.deployed(); + const storageLayout = await getStorageLayout('TestBytes'); + + let value = ethers.utils.hexlify(ethers.utils.randomBytes(10)); + await testBytes.setBytesTen(value); + let storageValue = await getStorageValue(testBytes.address, storageLayout, getStorageAt, 'bytesTen'); + expect(storageValue).to.equal(value); + + value = ethers.utils.hexlify(ethers.utils.randomBytes(20)); + await testBytes.setBytesTwenty(value); + storageValue = await getStorageValue(testBytes.address, storageLayout, getStorageAt, 'bytesTwenty'); + expect(storageValue).to.equal(value); + }); + + it('get value for fixed size byte arrays using single slot', async function () { + const TestBytes = await ethers.getContractFactory('TestBytes'); + const testBytes = await TestBytes.deploy(); + await testBytes.deployed(); + const storageLayout = await getStorageLayout('TestBytes'); + + const value = ethers.utils.hexlify(ethers.utils.randomBytes(30)); + await testBytes.setBytesThirty(value); + const storageValue = await getStorageValue(testBytes.address, storageLayout, getStorageAt, 'bytesThirty'); + expect(storageValue).to.equal(value); + }); + + it('get value for enum types', async function () { + const TestEnums = await ethers.getContractFactory('TestEnums'); + const testEnums = await TestEnums.deploy(); + await testEnums.deployed(); + const storageLayout = await getStorageLayout('TestEnums'); + + const value = 1; + await testEnums.setChoicesEnum1(value); + const storageValue = await getStorageValue(testEnums.address, storageLayout, getStorageAt, 'choicesEnum1'); + expect(storageValue).to.equal(value); + }); + + describe('string type', function () { let strings: Contract, storageLayout: StorageLayout; before(async () => { - const Strings = await hre.ethers.getContractFactory("TestStrings"); + const Strings = await ethers.getContractFactory('TestStrings'); strings = await Strings.deploy(); await strings.deployed(); - storageLayout = await getStorageLayout("TestStrings"); - }) + storageLayout = await getStorageLayout('TestStrings'); + }); - it("get value for string length less than 32 bytes", async function() { - const value = 'Hello world.' + it('get value for string length less than 32 bytes', async function () { + const value = 'Hello world.'; await strings.setString1(value); - const storageValue = await getStorageValue(strings.address, storageLayout, getStorageAt, "string1"); + const storageValue = await getStorageValue(strings.address, storageLayout, getStorageAt, 'string1'); expect(storageValue).to.equal(value); }); - it("get value for string length more than 32 bytes", async function() { - const value = 'This sentence is more than 32 bytes long.' + it('get value for string length more than 32 bytes', async function () { + const value = 'This sentence is more than 32 bytes long.'; await strings.setString2(value); - const storageValue = await getStorageValue(strings.address, storageLayout, getStorageAt, "string2"); + const storageValue = await getStorageValue(strings.address, storageLayout, getStorageAt, 'string2'); expect(storageValue).to.equal(value); }); - }) + }); }); diff --git a/packages/solidity-mapper/src/storage.ts b/packages/solidity-mapper/src/storage.ts index f37911c1..2950c39f 100644 --- a/packages/solidity-mapper/src/storage.ts +++ b/packages/solidity-mapper/src/storage.ts @@ -31,7 +31,7 @@ export type GetStorageAt = (address: string, position: string) => Promise { const { storage, types } = storageLayout; - const targetState = storage.find((state) => state.label === variableName) + const targetState = storage.find((state) => state.label === variableName); // Throw if state variable could not be found in storage layout. if (!targetState) { @@ -42,8 +42,8 @@ export const getStorageInfo = (storageLayout: StorageLayout, variableName: strin ...targetState, slot: utils.hexlify(BigNumber.from(targetState.slot)), types - } -} + }; +}; /** * Function to get the value from storage for a contract variable. @@ -54,7 +54,7 @@ export const getStorageInfo = (storageLayout: StorageLayout, variableName: strin */ export const getStorageValue = async (address: string, storageLayout: StorageLayout, getStorageAt: GetStorageAt, variableName: string): Promise => { const { slot, offset, type, types } = getStorageInfo(storageLayout, variableName); - const { encoding, numberOfBytes, label } = types[type] + const { encoding, numberOfBytes, label } = types[type]; // 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 @@ -63,31 +63,30 @@ export const getStorageValue = async (address: string, storageLayout: StorageLay case 'inplace': { const valueArray = await getInplaceArray(address, slot, offset, numberOfBytes, getStorageAt); - // Parse value for address type. - if (['address', 'address payable'].some(type => type === label)) { - return utils.hexlify(valueArray); - } - // Parse value for boolean type. if (label === 'bool') { return !BigNumber.from(valueArray).isZero(); } // Parse value for uint/int type. - return BigNumber.from(valueArray).toNumber(); + if (label.match(/^enum|u?int[0-9]+/)) { + return BigNumber.from(valueArray).toNumber(); + } + + return utils.hexlify(valueArray); } // https://docs.soliditylang.org/en/v0.8.4/internals/layout_in_storage.html#bytes-and-string case 'bytes': { const valueArray = await getBytesArray(address, slot, getStorageAt); - return utils.toUtf8String(valueArray) + return utils.toUtf8String(valueArray); } default: break; } -} +}; /** * Function to get array value for inplace encoding. @@ -104,10 +103,10 @@ const getInplaceArray = async (address: string, slot: string, offset: number, nu // Get value according to offset. const start = uintArray.length - (offset + Number(numberOfBytes)); const end = uintArray.length - offset; - const offsetArray = uintArray.slice(start, end) + const offsetArray = uintArray.slice(start, end); return offsetArray; -} +}; /** * Function to get array value for bytes encoding. @@ -116,7 +115,7 @@ const getInplaceArray = async (address: string, slot: string, offset: number, nu * @param getStorageAt */ const getBytesArray = async (address: string, slot: string, getStorageAt: GetStorageAt) => { - let value = await getStorageAt(address, slot); + const value = await getStorageAt(address, slot); const uintArray = utils.arrayify(value); let length = 0; @@ -145,11 +144,11 @@ const getBytesArray = async (address: string, slot: string, getStorageAt: GetSto const position = utils.keccak256(paddedSlotHex); // Get value from consecutive storage slots for longer data. - for(let i = 0; i < length / 32; i++) { + for (let i = 0; i < length / 32; i++) { const value = await getStorageAt(address, BigNumber.from(position).add(i).toHexString()); values.push(value); } // Slice trailing bytes according to length of value. return utils.concat(values).slice(0, length); -} +}; diff --git a/packages/solidity-mapper/test/contracts/TestAddress.sol b/packages/solidity-mapper/test/contracts/TestAddress.sol index 867e354a..a3399437 100644 --- a/packages/solidity-mapper/test/contracts/TestAddress.sol +++ b/packages/solidity-mapper/test/contracts/TestAddress.sol @@ -2,8 +2,10 @@ pragma solidity ^0.7.0; contract TestAddress { + // Address type need 20 bytes for storage. address address1; + // Address type uses the next slot as there is not enough space in previous slot. address payable address2; // Set variable address1. diff --git a/packages/solidity-mapper/test/contracts/TestBooleans.sol b/packages/solidity-mapper/test/contracts/TestBooleans.sol index 69792d44..8e65726a 100644 --- a/packages/solidity-mapper/test/contracts/TestBooleans.sol +++ b/packages/solidity-mapper/test/contracts/TestBooleans.sol @@ -2,7 +2,7 @@ pragma solidity ^0.7.0; contract TestBooleans { - // Variables are packed together in a slot as they occupy less than 32 bytes together. + // Boolean type variables are packed together in a slot as they occupy less than 32 bytes together. bool bool1; bool bool2; diff --git a/packages/solidity-mapper/test/contracts/TestBytes.sol b/packages/solidity-mapper/test/contracts/TestBytes.sol new file mode 100644 index 00000000..6edcb1cd --- /dev/null +++ b/packages/solidity-mapper/test/contracts/TestBytes.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +contract TestBytes { + // Byte array variables are packed together in a slot as they occupy less than 32 bytes together. + bytes10 bytesTen; + bytes20 bytesTwenty; + + // Byte array variable is stored in the next slot as there is not enough space for it in the previous slot. + bytes30 bytesThirty; + + // Dynamically sized byte arrays will take the next single slot. + // If data is 32 or more bytes, the main slot stores the value length * 2 + 1 and the data is stored in keccak256(slot). + // Else the main slot stores the data and value length * 2. + // https://docs.soliditylang.org/en/v0.7.4/internals/layout_in_storage.html#bytes-and-string + bytes bytesArray; + + // Set variable bytesTen. + function setBytesTen(bytes10 value) external { + bytesTen = value; + } + + // Set variable bytesTwenty. + function setBytesTwenty(bytes20 value) external { + bytesTwenty = value; + } + + // Set variable bytesThirty. + function setBytesThirty(bytes30 value) external { + bytesThirty = value; + } + + // Set variable bytesArray. + function setBytesArray(bytes calldata value) external { + bytesArray = value; + } +} diff --git a/packages/solidity-mapper/test/contracts/TestContractTypes.sol b/packages/solidity-mapper/test/contracts/TestContractTypes.sol new file mode 100644 index 00000000..bdca94b2 --- /dev/null +++ b/packages/solidity-mapper/test/contracts/TestContractTypes.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +import "./TestAddress.sol"; + +contract TestContractTypes { + // Contract types like address type need 20 bytes for storage. + TestAddress addressContract1; + + // Contract type variable uses the next slot as there is not enough space in previous slot. + TestAddress addressContract2; + + // Set variable addressContract1. + function setAddressContract1 (TestAddress value) external { + addressContract1 = value; + } + + // Set variable addressContract2. + function setAddressContract2 (TestAddress value) external { + addressContract2 = value; + } +} diff --git a/packages/solidity-mapper/test/contracts/TestEnums.sol b/packages/solidity-mapper/test/contracts/TestEnums.sol new file mode 100644 index 00000000..649fe2fe --- /dev/null +++ b/packages/solidity-mapper/test/contracts/TestEnums.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +contract TestEnums { + // Variables of this enum type will need 1 byte for storage. + enum Choices { Choice0, Choice1, Choice2, Choice3 } + + // Enum type variables are packed together in a slot as they occupy less than 32 bytes together. + Choices choicesEnum1; + Choices choicesEnum2; + + // Set variable choicesEnum1. + function setChoicesEnum1(Choices value) external { + choicesEnum1 = value; + } +} diff --git a/packages/solidity-mapper/test/contracts/TestEvents.sol b/packages/solidity-mapper/test/contracts/TestEvents.sol new file mode 100644 index 00000000..902a3cde --- /dev/null +++ b/packages/solidity-mapper/test/contracts/TestEvents.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +contract TestEvents { + event Event1(address address1, string string1, string strin2); + + // Function to emit event. + function emitEvent(string calldata string1, string calldata string2) external { + emit Event1(msg.sender, string1, string2); + } +} diff --git a/packages/solidity-mapper/test/contracts/TestFixedArrays.sol b/packages/solidity-mapper/test/contracts/TestFixedArrays.sol new file mode 100644 index 00000000..1b8d5e9b --- /dev/null +++ b/packages/solidity-mapper/test/contracts/TestFixedArrays.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +contract TestFixedArrays { + // Fixed size array variable will use 5 consecutive slots as size of 1 element is 32 bytes. + uint[5] uintArray; + + // Fixed size array variable will use 10 consecutive slots as size of 1 element is 32 bytes. + int[10] intArray; + + // Fixed size array variable will use 1 slot as size of one element is 1 byte. + int8[2] int8Array; + + // Fixed size array variable will use the next consecutive slot as it is of array type. + // https://docs.soliditylang.org/en/v0.7.4/internals/layout_in_storage.html#layout-of-state-variables-in-storage + uint128[5] uint128Array; + + // Set varaible uintArray. + function setUintArray(uint[5] calldata value) external { + uintArray = value; + } + + // Set varaible int8Array. + function setInt8Array(int8[2] calldata value) external { + int8Array = value; + } + + // Set varaible uint128Array. + function setUint128Array(uint128[5] calldata value) external { + uint128Array = value; + } +} diff --git a/packages/solidity-mapper/test/contracts/TestIntegers.sol b/packages/solidity-mapper/test/contracts/TestIntegers.sol index 4f45b9ee..f0c5bb11 100644 --- a/packages/solidity-mapper/test/contracts/TestIntegers.sol +++ b/packages/solidity-mapper/test/contracts/TestIntegers.sol @@ -2,14 +2,14 @@ pragma solidity ^0.7.0; contract TestIntegers { - // Following variables are packed together in a single slot since the combined size is less than 32 bytes. + // Following integer type variables are packed together in a single slot since the combined size is less than 32 bytes. int8 int1; int16 int2; - // Variable is stored in the next slot as it needs 32 bytes of storage. + // Integer type variable is stored in the next slot as it needs 32 bytes of storage. int256 int3; - // Variable is stored in the next slot as there is not enough space for it in the previous slot. + // Integer type variable is stored in the next slot as there is not enough space for it in the previous slot. int32 int4; // Set variable int1. diff --git a/packages/solidity-mapper/test/contracts/TestStrings.sol b/packages/solidity-mapper/test/contracts/TestStrings.sol index 0d96591d..428d3e7b 100644 --- a/packages/solidity-mapper/test/contracts/TestStrings.sol +++ b/packages/solidity-mapper/test/contracts/TestStrings.sol @@ -4,6 +4,10 @@ pragma solidity ^0.7.0; contract TestStrings { string string1; + // String type variable takes the next single slot. + // If data is 32 or more bytes, the main slot stores the value length * 2 + 1 and the data is stored in keccak256(slot). + // Else the main slot stores the data and value length * 2. + // https://docs.soliditylang.org/en/v0.7.4/internals/layout_in_storage.html#bytes-and-string string string2; // Set variable string1. diff --git a/packages/solidity-mapper/test/contracts/TestUnsignedIntegers.sol b/packages/solidity-mapper/test/contracts/TestUnsignedIntegers.sol index 7c9a93fb..43f583b7 100644 --- a/packages/solidity-mapper/test/contracts/TestUnsignedIntegers.sol +++ b/packages/solidity-mapper/test/contracts/TestUnsignedIntegers.sol @@ -2,14 +2,14 @@ pragma solidity ^0.7.0; contract TestUnsignedIntegers { - // Following variables are packed together in a single slot since the combined size is less than 32 bytes. + // Following unsigned integer variables are packed together in a single slot since the combined size is less than 32 bytes. uint8 uint1; uint16 uint2; - // Variable is stored in the next slot as it needs 32 bytes of storage. + // Unsigned integer variable is stored in the next slot as it needs 32 bytes of storage. uint256 uint3; - // Variable is stored in the next slot as there is not enough space for it in the previous slot. + // Unsigned integer variable is stored in the next slot as there is not enough space for it in the previous slot. uint32 uint4; // Set variable uint1. diff --git a/packages/solidity-mapper/test/utils.ts b/packages/solidity-mapper/test/utils.ts index a0bfaa90..68aff2d3 100644 --- a/packages/solidity-mapper/test/utils.ts +++ b/packages/solidity-mapper/test/utils.ts @@ -1,4 +1,5 @@ -import { artifacts, ethers } from 'hardhat' +import { ContractInterface } from '@ethersproject/contracts'; +import { artifacts, ethers } from 'hardhat'; import { CompilerOutput, CompilerOutputBytecode } from 'hardhat/types'; import { StorageLayout, GetStorageAt } from '../src'; @@ -9,7 +10,7 @@ interface StorageCompilerOutput extends CompilerOutput { contracts: { [sourceName: string]: { [contractName: string]: { - abi: any; + abi: ContractInterface; evm: { bytecode: CompilerOutputBytecode; deployedBytecode: CompilerOutputBytecode; @@ -27,23 +28,23 @@ interface StorageCompilerOutput extends CompilerOutput { * Get storage layout of specified contract. * @param contractName */ -export const getStorageLayout = async (contractName: string) => { +export const getStorageLayout = async (contractName: string): Promise => { const artifact = await artifacts.readArtifact(contractName); - const buildInfo = await artifacts.getBuildInfo(`${artifact.sourceName}:${artifact.contractName}`) + const buildInfo = await artifacts.getBuildInfo(`${artifact.sourceName}:${artifact.contractName}`); if (!buildInfo) { throw new Error('storageLayout not present in compiler output.'); } - const output: StorageCompilerOutput = buildInfo.output + const output: StorageCompilerOutput = buildInfo.output; const { storageLayout } = output.contracts[artifact.sourceName][artifact.contractName]; if (!storageLayout) { - throw new Error(`Contract hasn't been compiled.`); + throw new Error('Contract hasn\'t been compiled.'); } return storageLayout; -} +}; /** * Get storage value in hardhat environment using ethers. @@ -54,4 +55,4 @@ export const getStorageAt: GetStorageAt = async (address, position) => { const value = await ethers.provider.getStorageAt(address, position); return value; -} +};