mirror of
https://github.com/cerc-io/watcher-ts
synced 2025-01-08 12:28:05 +00:00
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:
parent
f6870d88dc
commit
b243025ca8
@ -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
|
||||||
|
|
||||||
|
@ -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: {
|
||||||
|
33
packages/solidity-mapper/src/logs.test.ts
Normal file
33
packages/solidity-mapper/src/logs.test.ts
Normal 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);
|
||||||
|
});
|
@ -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;
|
||||||
}, {})
|
}, {});
|
||||||
}
|
};
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
|
@ -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);
|
||||||
}
|
};
|
||||||
|
@ -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.
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
37
packages/solidity-mapper/test/contracts/TestBytes.sol
Normal file
37
packages/solidity-mapper/test/contracts/TestBytes.sol
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
16
packages/solidity-mapper/test/contracts/TestEnums.sol
Normal file
16
packages/solidity-mapper/test/contracts/TestEnums.sol
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
11
packages/solidity-mapper/test/contracts/TestEvents.sol
Normal file
11
packages/solidity-mapper/test/contracts/TestEvents.sol
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
32
packages/solidity-mapper/test/contracts/TestFixedArrays.sol
Normal file
32
packages/solidity-mapper/test/contracts/TestFixedArrays.sol
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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;
|
||||||
}
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user