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>
This commit is contained in:
Ashwin Phatak 2021-06-02 11:23:33 +05:30 committed by GitHub
parent f6870d88dc
commit b243025ca8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 420 additions and 102 deletions

View File

@ -21,11 +21,14 @@ $ yarn test
* [x] Integers * [x] Integers
* [ ] Fixed Point Numbers * [ ] Fixed Point Numbers
* [x] Address * [x] Address
* [ ] Contract Types * [x] Contract Types
* [ ] Fixed-size byte arrays * [x] Fixed-size byte arrays
* [ ] Enums * [x] Enums
* [ ] Function Types * [ ] Function Types
* [ ] Arrays * [ ] Arrays
* [ ] Dynamically-sized byte array
* [ ] Bytes
* [x] String
* [ ] Structs * [ ] Structs
* [ ] Mapping Types * [ ] Mapping Types

View File

@ -1,9 +1,9 @@
import { task, HardhatUserConfig } from "hardhat/config"; import { task, HardhatUserConfig } from 'hardhat/config';
import "@nomiclabs/hardhat-waffle"; import '@nomiclabs/hardhat-waffle';
// This is a sample Hardhat task. To learn how to create your own go to // This is a sample Hardhat task. To learn how to create your own go to
// https://hardhat.org/guides/create-task.html // 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(); const accounts = await hre.ethers.getSigners();
for (const account of accounts) { for (const account of accounts) {
@ -16,20 +16,20 @@ task("accounts", "Prints the list of accounts", async (args, hre) => {
const config: HardhatUserConfig = { const config: HardhatUserConfig = {
solidity: { solidity: {
version: "0.7.3", version: '0.7.3',
settings: { settings: {
outputSelection: { outputSelection: {
"*": { '*': {
"*": [ '*': [
"abi", "storageLayout", 'abi', 'storageLayout',
"metadata", "evm.bytecode", // Enable the metadata and bytecode outputs of every single contract. '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. '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: { paths: {

View File

@ -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);
});

View File

@ -1,7 +1,7 @@
import { JsonFragment } from "@ethersproject/abi" import { JsonFragment } from '@ethersproject/abi';
import { utils } from "ethers"; import { utils } from 'ethers';
interface EventNameTopic { interface EventNameTopics {
[eventName: string]: string [eventName: string]: string
} }
@ -9,10 +9,10 @@ interface EventNameTopic {
* Function to get event name topics from abi. * Function to get event name topics from abi.
* @param abi * @param abi
*/ */
export const getEventNameTopics = (abi: JsonFragment[]): EventNameTopic => { export const getEventNameTopics = (abi: JsonFragment[]): EventNameTopics => {
const eventFragments = abi.filter(({ type }) => type === 'event'); const eventFragments = abi.filter(({ type }) => type === 'event');
return eventFragments.reduce((acc: EventNameTopic, { name, inputs }) => { return eventFragments.reduce((acc: EventNameTopics, { name, inputs }) => {
if (inputs && name) { if (inputs && name) {
const inputParamsString = inputs.map(({ type }) => type) const inputParamsString = inputs.map(({ type }) => type)
.join(','); .join(',');
@ -22,5 +22,5 @@ export const getEventNameTopics = (abi: JsonFragment[]): EventNameTopic => {
} }
return acc; return acc;
}, {}) }, {});
} };

View File

@ -1,89 +1,247 @@
import { Contract } from "@ethersproject/contracts"; import { Contract } from '@ethersproject/contracts';
import { expect } from "chai"; import { expect } from 'chai';
import hre from "hardhat"; import { ethers } from 'hardhat';
import "@nomiclabs/hardhat-ethers"; import '@nomiclabs/hardhat-ethers';
import { getStorageValue, StorageLayout } from "./storage"; import { getStorageInfo, getStorageValue, StorageLayout } from './storage';
import { getStorageLayout, getStorageAt } from "../test/utils"; import { getStorageLayout, getStorageAt } from '../test/utils';
describe("Storage", function() { const TEST_DATA = [
it("get value for integer type", async function() { {
const Integers = await hre.ethers.getContractFactory("TestIntegers"); 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(); const integers = await Integers.deploy();
await integers.deployed(); await integers.deployed();
const storageLayout = await getStorageLayout("TestIntegers"); const storageLayout = await getStorageLayout('TestIntegers');
// if (storageLayout)
let value = 12; let value = 12;
await integers.setInt1(value); 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); expect(storageValue).to.equal(value);
}); });
it("get value for unsigned integer type", async function() { it('get value for integer type variables using single slot', async function () {
const UnsignedIntegers = await hre.ethers.getContractFactory("TestUnsignedIntegers"); const Integers = await ethers.getContractFactory('TestIntegers');
const unsignedIntegers = await UnsignedIntegers.deploy(); const integers = await Integers.deploy();
await unsignedIntegers.deployed(); await integers.deployed();
const storageLayout = await getStorageLayout("TestUnsignedIntegers"); const storageLayout = await getStorageLayout('TestIntegers');
const value = 123; const value = 123;
await unsignedIntegers.setUint1(value); await integers.setInt3(value);
const storageValue = await getStorageValue(unsignedIntegers.address, storageLayout, getStorageAt, "uint1"); const storageValue = await getStorageValue(integers.address, storageLayout, getStorageAt, 'int3');
expect(storageValue).to.equal(value); expect(storageValue).to.equal(value);
}); });
it("get value for boolean type", async function() { it('get value for unsigned integer type variables packed together', async function () {
const Booleans = await hre.ethers.getContractFactory("TestBooleans"); const UnsignedIntegers = await ethers.getContractFactory('TestUnsignedIntegers');
const booleans = await Booleans.deploy(); const unsignedIntegers = await UnsignedIntegers.deploy();
await booleans.deployed(); await unsignedIntegers.deployed();
const storageLayout = await getStorageLayout("TestBooleans"); const storageLayout = await getStorageLayout('TestUnsignedIntegers');
let value = true let value = 12;
await booleans.setBool1(value); await unsignedIntegers.setUint1(value);
let storageValue = await getStorageValue(booleans.address, storageLayout, getStorageAt, "bool1"); let storageValue = await getStorageValue(unsignedIntegers.address, storageLayout, getStorageAt, 'uint1');
expect(storageValue).to.equal(value) expect(storageValue).to.equal(value);
value = false value = 34;
await booleans.setBool2(value); await unsignedIntegers.setUint2(value);
storageValue = await getStorageValue(booleans.address, storageLayout, getStorageAt, "bool2") storageValue = await getStorageValue(unsignedIntegers.address, storageLayout, getStorageAt, 'uint2');
expect(storageValue).to.equal(value) expect(storageValue).to.equal(value);
}); });
it("get value for address type", async function() { it('get value for unsigned integer type variables using single slot', async function () {
const Address = await hre.ethers.getContractFactory("TestAddress"); 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(); const address = await Address.deploy();
await address.deployed(); 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); 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(storageValue).to.be.a('string');
expect(String(storageValue).toLowerCase()).to.equal(signer.address.toLowerCase()); 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; let strings: Contract, storageLayout: StorageLayout;
before(async () => { before(async () => {
const Strings = await hre.ethers.getContractFactory("TestStrings"); const Strings = await ethers.getContractFactory('TestStrings');
strings = await Strings.deploy(); strings = await Strings.deploy();
await strings.deployed(); await strings.deployed();
storageLayout = await getStorageLayout("TestStrings"); storageLayout = await getStorageLayout('TestStrings');
}) });
it("get value for string length less than 32 bytes", async function() { it('get value for string length less than 32 bytes', async function () {
const value = 'Hello world.' const value = 'Hello world.';
await strings.setString1(value); 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); expect(storageValue).to.equal(value);
}); });
it("get value for string length more than 32 bytes", async function() { it('get value for string length more than 32 bytes', async function () {
const value = 'This sentence is more than 32 bytes long.' const value = 'This sentence is more than 32 bytes long.';
await strings.setString2(value); 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); expect(storageValue).to.equal(value);
}); });
}) });
}); });

View File

@ -31,7 +31,7 @@ export type GetStorageAt = (address: string, position: string) => Promise<string
*/ */
export const getStorageInfo = (storageLayout: StorageLayout, variableName: string): StorageInfo => { export const getStorageInfo = (storageLayout: StorageLayout, variableName: string): StorageInfo => {
const { storage, types } = storageLayout; 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. // Throw if state variable could not be found in storage layout.
if (!targetState) { if (!targetState) {
@ -42,8 +42,8 @@ export const getStorageInfo = (storageLayout: StorageLayout, variableName: strin
...targetState, ...targetState,
slot: utils.hexlify(BigNumber.from(targetState.slot)), slot: utils.hexlify(BigNumber.from(targetState.slot)),
types types
} };
} };
/** /**
* Function to get the value from storage for a contract variable. * 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<number | string | boolean | undefined> => { export const getStorageValue = async (address: string, storageLayout: StorageLayout, getStorageAt: GetStorageAt, variableName: string): Promise<number | string | boolean | undefined> => {
const { slot, offset, type, types } = getStorageInfo(storageLayout, variableName); 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. // 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 // 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': { case 'inplace': {
const valueArray = await getInplaceArray(address, slot, offset, numberOfBytes, getStorageAt); 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. // Parse value for boolean type.
if (label === 'bool') { if (label === 'bool') {
return !BigNumber.from(valueArray).isZero(); return !BigNumber.from(valueArray).isZero();
} }
// Parse value for uint/int type. // 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 // https://docs.soliditylang.org/en/v0.8.4/internals/layout_in_storage.html#bytes-and-string
case 'bytes': { case 'bytes': {
const valueArray = await getBytesArray(address, slot, getStorageAt); const valueArray = await getBytesArray(address, slot, getStorageAt);
return utils.toUtf8String(valueArray) return utils.toUtf8String(valueArray);
} }
default: default:
break; break;
} }
} };
/** /**
* Function to get array value for inplace encoding. * 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. // Get value according to offset.
const start = uintArray.length - (offset + Number(numberOfBytes)); const start = uintArray.length - (offset + Number(numberOfBytes));
const end = uintArray.length - offset; const end = uintArray.length - offset;
const offsetArray = uintArray.slice(start, end) const offsetArray = uintArray.slice(start, end);
return offsetArray; return offsetArray;
} };
/** /**
* Function to get array value for bytes encoding. * Function to get array value for bytes encoding.
@ -116,7 +115,7 @@ const getInplaceArray = async (address: string, slot: string, offset: number, nu
* @param getStorageAt * @param getStorageAt
*/ */
const getBytesArray = async (address: string, slot: string, getStorageAt: 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); const uintArray = utils.arrayify(value);
let length = 0; let length = 0;
@ -145,11 +144,11 @@ const getBytesArray = async (address: string, slot: string, getStorageAt: GetSto
const position = utils.keccak256(paddedSlotHex); const position = utils.keccak256(paddedSlotHex);
// Get value from consecutive storage slots for longer data. // 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()); const value = await getStorageAt(address, BigNumber.from(position).add(i).toHexString());
values.push(value); values.push(value);
} }
// Slice trailing bytes according to length of value. // Slice trailing bytes according to length of value.
return utils.concat(values).slice(0, length); return utils.concat(values).slice(0, length);
} };

View File

@ -2,8 +2,10 @@
pragma solidity ^0.7.0; pragma solidity ^0.7.0;
contract TestAddress { contract TestAddress {
// Address type need 20 bytes for storage.
address address1; address address1;
// Address type uses the next slot as there is not enough space in previous slot.
address payable address2; address payable address2;
// Set variable address1. // Set variable address1.

View File

@ -2,7 +2,7 @@
pragma solidity ^0.7.0; pragma solidity ^0.7.0;
contract TestBooleans { 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 bool1;
bool bool2; bool bool2;

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -2,14 +2,14 @@
pragma solidity ^0.7.0; pragma solidity ^0.7.0;
contract TestIntegers { 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; int8 int1;
int16 int2; 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; 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; int32 int4;
// Set variable int1. // Set variable int1.

View File

@ -4,6 +4,10 @@ pragma solidity ^0.7.0;
contract TestStrings { contract TestStrings {
string string1; 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; string string2;
// Set variable string1. // Set variable string1.

View File

@ -2,14 +2,14 @@
pragma solidity ^0.7.0; pragma solidity ^0.7.0;
contract TestUnsignedIntegers { 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; uint8 uint1;
uint16 uint2; 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; 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; uint32 uint4;
// Set variable uint1. // Set variable uint1.

View File

@ -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 { CompilerOutput, CompilerOutputBytecode } from 'hardhat/types';
import { StorageLayout, GetStorageAt } from '../src'; import { StorageLayout, GetStorageAt } from '../src';
@ -9,7 +10,7 @@ interface StorageCompilerOutput extends CompilerOutput {
contracts: { contracts: {
[sourceName: string]: { [sourceName: string]: {
[contractName: string]: { [contractName: string]: {
abi: any; abi: ContractInterface;
evm: { evm: {
bytecode: CompilerOutputBytecode; bytecode: CompilerOutputBytecode;
deployedBytecode: CompilerOutputBytecode; deployedBytecode: CompilerOutputBytecode;
@ -27,23 +28,23 @@ interface StorageCompilerOutput extends CompilerOutput {
* Get storage layout of specified contract. * Get storage layout of specified contract.
* @param contractName * @param contractName
*/ */
export const getStorageLayout = async (contractName: string) => { export const getStorageLayout = async (contractName: string): Promise<StorageLayout> => {
const artifact = await artifacts.readArtifact(contractName); 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) { if (!buildInfo) {
throw new Error('storageLayout not present in compiler output.'); 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]; const { storageLayout } = output.contracts[artifact.sourceName][artifact.contractName];
if (!storageLayout) { if (!storageLayout) {
throw new Error(`Contract hasn't been compiled.`); throw new Error('Contract hasn\'t been compiled.');
} }
return storageLayout; return storageLayout;
} };
/** /**
* Get storage value in hardhat environment using ethers. * 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); const value = await ethers.provider.getStorageAt(address, position);
return value; return value;
} };