watcher-ts/packages/solidity-mapper/src/storage.test.ts

594 lines
26 KiB
TypeScript
Raw Normal View History

import { Contract } from '@ethersproject/contracts';
import { expect } from 'chai';
import { ethers } from 'hardhat';
import '@nomiclabs/hardhat-ethers';
import { getStorageInfo, getStorageValue, StorageLayout } from './storage';
import { getStorageLayout, getStorageAt } from '../test/utils';
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 () => {
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', () => {
const getBlockHash = async () => {
const blockNumber = await ethers.provider.getBlockNumber();
const { hash } = await ethers.provider.getBlock(blockNumber);
return hash;
};
it('get value for integer type variables packed together', async () => {
const Integers = await ethers.getContractFactory('TestIntegers');
const integers = await Integers.deploy();
await integers.deployed();
const storageLayout = await getStorageLayout('TestIntegers');
let expectedValue = 12;
await integers.setInt1(expectedValue);
let blockHash = await getBlockHash();
let { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, integers.address, 'int1');
expect(value).to.equal(BigInt(expectedValue));
expectedValue = 34;
await integers.setInt2(expectedValue);
blockHash = await getBlockHash();
({ value } = await getStorageValue(storageLayout, getStorageAt, blockHash, integers.address, 'int2'));
expect(value).to.equal(BigInt(expectedValue));
});
it('get value for integer type variables using single slot', async () => {
const Integers = await ethers.getContractFactory('TestIntegers');
const integers = await Integers.deploy();
await integers.deployed();
const storageLayout = await getStorageLayout('TestIntegers');
const expectedValue = 123;
await integers.setInt3(expectedValue);
const blockHash = await getBlockHash();
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, integers.address, 'int3');
expect(value).to.equal(BigInt(expectedValue));
});
it('get value for unsigned integer type variables packed together', async () => {
const UnsignedIntegers = await ethers.getContractFactory('TestUnsignedIntegers');
const unsignedIntegers = await UnsignedIntegers.deploy();
await unsignedIntegers.deployed();
const storageLayout = await getStorageLayout('TestUnsignedIntegers');
let expectedValue = 12;
await unsignedIntegers.setUint1(expectedValue);
let blockHash = await getBlockHash();
let { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, unsignedIntegers.address, 'uint1');
expect(value).to.equal(BigInt(expectedValue));
expectedValue = 34;
await unsignedIntegers.setUint2(expectedValue);
blockHash = await getBlockHash();
({ value } = await getStorageValue(storageLayout, getStorageAt, blockHash, unsignedIntegers.address, 'uint2'));
expect(value).to.equal(BigInt(expectedValue));
});
it('get value for unsigned integer type variables using single slot', async () => {
const UnsignedIntegers = await ethers.getContractFactory('TestUnsignedIntegers');
const unsignedIntegers = await UnsignedIntegers.deploy();
await unsignedIntegers.deployed();
const storageLayout = await getStorageLayout('TestUnsignedIntegers');
const expectedValue = 123;
await unsignedIntegers.setUint3(expectedValue);
const blockHash = await getBlockHash();
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, unsignedIntegers.address, 'uint3');
expect(value).to.equal(BigInt(expectedValue));
});
it('get value for boolean type', async () => {
const Booleans = await ethers.getContractFactory('TestBooleans');
const booleans = await Booleans.deploy();
await booleans.deployed();
const storageLayout = await getStorageLayout('TestBooleans');
let expectedValue = true;
await booleans.setBool1(expectedValue);
let blockHash = await getBlockHash();
let { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, booleans.address, 'bool1');
expect(value).to.equal(expectedValue);
expectedValue = false;
await booleans.setBool2(expectedValue);
blockHash = await getBlockHash();
({ value } = await getStorageValue(storageLayout, getStorageAt, blockHash, booleans.address, 'bool2'));
expect(value).to.equal(expectedValue);
});
it('get value for address type', async () => {
const Address = await ethers.getContractFactory('TestAddress');
const address = await Address.deploy();
await address.deployed();
const storageLayout = await getStorageLayout('TestAddress');
const [signer] = await ethers.getSigners();
await address.setAddress1(signer.address);
const blockHash = await getBlockHash();
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, address.address, 'address1');
expect(value).to.be.a('string');
expect(String(value).toLowerCase()).to.equal(signer.address.toLowerCase());
});
it('get value for contract type', async () => {
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 blockHash = await getBlockHash();
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testContractTypes.address, 'addressContract1');
expect(value).to.equal(testAddress.address.toLowerCase());
});
it('get value for fixed size byte arrays packed together', async () => {
const TestBytes = await ethers.getContractFactory('TestBytes');
const testBytes = await TestBytes.deploy();
await testBytes.deployed();
const storageLayout = await getStorageLayout('TestBytes');
let expectedValue = ethers.utils.hexlify(ethers.utils.randomBytes(10));
await testBytes.setBytesTen(expectedValue);
let blockHash = await getBlockHash();
let { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testBytes.address, 'bytesTen');
expect(value).to.equal(expectedValue);
expectedValue = ethers.utils.hexlify(ethers.utils.randomBytes(20));
await testBytes.setBytesTwenty(expectedValue);
blockHash = await getBlockHash();
({ value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testBytes.address, 'bytesTwenty'));
expect(value).to.equal(expectedValue);
});
it('get value for fixed size byte arrays using single slot', async () => {
const TestBytes = await ethers.getContractFactory('TestBytes');
const testBytes = await TestBytes.deploy();
await testBytes.deployed();
const storageLayout = await getStorageLayout('TestBytes');
const expectedValue = ethers.utils.hexlify(ethers.utils.randomBytes(30));
await testBytes.setBytesThirty(expectedValue);
const blockHash = await getBlockHash();
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testBytes.address, 'bytesThirty');
expect(value).to.equal(expectedValue);
});
it('get value for enum types', async () => {
const TestEnums = await ethers.getContractFactory('TestEnums');
const testEnums = await TestEnums.deploy();
await testEnums.deployed();
const storageLayout = await getStorageLayout('TestEnums');
const expectedValue = 1;
await testEnums.setChoicesEnum1(expectedValue);
const blockHash = await getBlockHash();
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testEnums.address, 'choicesEnum1');
expect(value).to.equal(BigInt(expectedValue));
});
describe('string type', () => {
let strings: Contract, storageLayout: StorageLayout;
before(async () => {
const Strings = await ethers.getContractFactory('TestStrings');
strings = await Strings.deploy();
await strings.deployed();
storageLayout = await getStorageLayout('TestStrings');
});
// Test for string of size less than 32 bytes which use only one slot.
it('get value for string length less than 32 bytes', async () => {
const expectedValue = 'Hello world.';
await strings.setString1(expectedValue);
const blockHash = await getBlockHash();
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, strings.address, 'string1');
expect(value).to.equal(expectedValue);
});
// Test for string of size 32 bytes or more which use multiple slots.
it('get value for string length more than 32 bytes', async () => {
const expectedValue = 'This sentence is more than 32 bytes long.';
await strings.setString2(expectedValue);
const blockHash = await getBlockHash();
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, strings.address, 'string2');
expect(value).to.equal(expectedValue);
});
});
describe('fixed size arrays', () => {
let testFixedArrays: Contract, storageLayout: StorageLayout;
before(async () => {
const TestFixedArrays = await ethers.getContractFactory('TestFixedArrays');
testFixedArrays = await TestFixedArrays.deploy();
await testFixedArrays.deployed();
storageLayout = await getStorageLayout('TestFixedArrays');
});
// Test for array variables which are 32 bytes or less and packed into a single slot.
it('get value for fixed size arrays using single slot', async () => {
let expectedValue: Array<number|boolean> = [true, false];
await testFixedArrays.setBoolArray(expectedValue);
let blockHash = await getBlockHash();
let { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testFixedArrays.address, 'boolArray');
expect(value).to.eql(expectedValue);
let proofData = JSON.parse(proof.data);
expect(proofData.length).to.equal(expectedValue.length);
expectedValue = [1, 2, 3, 4, 5];
await testFixedArrays.setUint16Array(expectedValue);
blockHash = await getBlockHash();
({ value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testFixedArrays.address, 'uint16Array'));
expect(value).to.eql(expectedValue.map(el => BigInt(el)));
proofData = JSON.parse(proof.data);
expect(proofData.length).to.equal(expectedValue.length);
});
// Test for array variables which are more than 32 bytes and use multiple slots.
it('get value for fixed size arrays using multiple slots', async () => {
const expectedValue = [1, 2, 3, 4, 5];
await testFixedArrays.setInt128Array(expectedValue);
let blockHash = await getBlockHash();
let { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testFixedArrays.address, 'int128Array');
expect(value).to.eql(expectedValue.map(el => BigInt(el)));
let proofData = JSON.parse(proof.data);
expect(proofData.length).to.equal(expectedValue.length);
await testFixedArrays.setUintArray(expectedValue);
blockHash = await getBlockHash();
({ value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testFixedArrays.address, 'uintArray'));
expect(value).to.eql(expectedValue.map(el => BigInt(el)));
proofData = JSON.parse(proof.data);
expect(proofData.length).to.equal(expectedValue.length);
});
it('get value for fixed size arrays of address type', async () => {
const signers = await ethers.getSigners();
const expectedValue = signers.map(signer => signer.address.toLowerCase())
.slice(0, 4);
await testFixedArrays.setAddressArray(expectedValue);
const blockHash = await getBlockHash();
const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testFixedArrays.address, 'addressArray');
expect(value).to.eql(expectedValue);
const proofData = JSON.parse(proof.data);
expect(proofData.length).to.equal(expectedValue.length);
});
it.skip('get value for fixed size arrays of fixed size bytes type', async () => {
const expectedValue = Array.from({ length: 5 }, () => ethers.utils.hexlify(ethers.utils.randomBytes(10)));
await testFixedArrays.setBytesArray(expectedValue);
const blockHash = await getBlockHash();
const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testFixedArrays.address, 'bytesArray');
expect(value).to.eql(expectedValue);
const proofData = JSON.parse(proof.data);
expect(proofData.length).to.equal(expectedValue.length);
});
});
describe('structs type', () => {
let testStructs: Contract, storageLayout: StorageLayout;
before(async () => {
const TestStructs = await ethers.getContractFactory('TestStructs');
testStructs = await TestStructs.deploy();
await testStructs.deployed();
storageLayout = await getStorageLayout('TestStructs');
});
it('get value for struct using a single slot', async () => {
const expectedValue = {
int1: BigInt(123),
uint1: BigInt(4)
};
await testStructs.setSingleSlotStruct(expectedValue.int1, expectedValue.uint1);
const blockHash = await getBlockHash();
const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testStructs.address, 'singleSlotStruct');
expect(value).to.eql(expectedValue);
const proofData = JSON.parse(proof.data);
expect(proofData).to.have.all.keys('int1', 'uint1');
});
it('get value for struct using multiple slots', async () => {
const expectedValue = {
uint1: BigInt(123),
bool1: false,
int1: BigInt(456)
};
await testStructs.setMultipleSlotStruct(expectedValue.uint1, expectedValue.bool1, expectedValue.int1);
const blockHash = await getBlockHash();
const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testStructs.address, 'multipleSlotStruct');
expect(value).to.eql(expectedValue);
const proofData = JSON.parse(proof.data);
expect(proofData).to.have.all.keys('uint1', 'bool1', 'int1');
});
it('get value for struct with address type members', async () => {
const [signer1, signer2] = await ethers.getSigners();
const expectedValue = {
int1: BigInt(123),
address1: signer1.address.toLowerCase(),
address2: signer2.address.toLowerCase(),
uint1: BigInt(456)
};
await testStructs.setAddressStruct(expectedValue.int1, expectedValue.address1, expectedValue.address2, expectedValue.uint1);
const blockHash = await getBlockHash();
const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testStructs.address, 'addressStruct');
expect(value).to.eql(expectedValue);
const proofData = JSON.parse(proof.data);
expect(proofData).to.have.all.keys('int1', 'address1', 'address2', 'uint1');
});
it('get value for struct with contract type members', async () => {
const Contract = await ethers.getContractFactory('TestContractTypes');
const contract = await Contract.deploy();
await contract.deployed();
const expectedValue = {
uint1: BigInt(123),
testContract: contract.address.toLowerCase()
};
await testStructs.setContractStruct(expectedValue.uint1, expectedValue.testContract);
const blockHash = await getBlockHash();
const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testStructs.address, 'contractStruct');
expect(value).to.eql(expectedValue);
const proofData = JSON.parse(proof.data);
expect(proofData).to.have.all.keys('uint1', 'testContract');
});
it('get value for struct with fixed-sized byte array members', async () => {
const expectedValue = {
uint1: BigInt(123),
bytesTen: ethers.utils.hexlify(ethers.utils.randomBytes(10)),
bytesTwenty: ethers.utils.hexlify(ethers.utils.randomBytes(20))
};
await testStructs.setFixedBytesStruct(expectedValue.uint1, expectedValue.bytesTen, expectedValue.bytesTwenty);
const blockHash = await getBlockHash();
const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testStructs.address, 'fixedBytesStruct');
expect(value).to.eql(expectedValue);
const proofData = JSON.parse(proof.data);
expect(proofData).to.have.all.keys('uint1', 'bytesTen', 'bytesTwenty');
});
it('get value for struct with enum type members', async () => {
const expectedValue = {
uint1: BigInt(123),
choice1: BigInt(2),
choice2: BigInt(3)
};
await testStructs.setEnumStruct(expectedValue.uint1, expectedValue.choice1, expectedValue.choice2);
const blockHash = await getBlockHash();
const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testStructs.address, 'enumStruct');
expect(value).to.eql(expectedValue);
const proofData = JSON.parse(proof.data);
expect(proofData).to.have.all.keys('uint1', 'choice1', 'choice2');
});
});
describe('basic mapping type', () => {
let testMappingTypes: Contract, storageLayout: StorageLayout;
before(async () => {
const TestMappingTypes = await ethers.getContractFactory('TestBasicMapping');
testMappingTypes = await TestMappingTypes.deploy();
await testMappingTypes.deployed();
storageLayout = await getStorageLayout('TestBasicMapping');
});
it('get value for mapping with address type keys', async () => {
const expectedValue = 123;
const [, signer1] = await ethers.getSigners();
await testMappingTypes.connect(signer1).setAddressUintMap(expectedValue);
const blockHash = await getBlockHash();
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testMappingTypes.address, 'addressUintMap', signer1.address);
expect(value).to.equal(BigInt(expectedValue));
});
it('get value for mapping with boolean type keys', async () => {
const expectedValue = 123;
const mapKey = true;
await testMappingTypes.setBoolIntMap(mapKey, expectedValue);
const blockHash = await getBlockHash();
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testMappingTypes.address, 'boolIntMap', mapKey);
expect(value).to.equal(BigInt(expectedValue));
});
it('get value for mapping with signed integer type keys', async () => {
const mapKey = 123;
const [, signer1] = await ethers.getSigners();
await testMappingTypes.setIntAddressMap(mapKey, signer1.address);
const blockHash = await getBlockHash();
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testMappingTypes.address, 'intAddressMap', mapKey);
expect(value).to.equal(signer1.address.toLowerCase());
});
it('get value for mapping with unsigned integer type keys', async () => {
const expectedValue = ethers.utils.hexlify(ethers.utils.randomBytes(16));
const mapKey = 123;
await testMappingTypes.setUintBytesMap(mapKey, expectedValue);
const blockHash = await getBlockHash();
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testMappingTypes.address, 'uintBytesMap', mapKey);
expect(value).to.equal(expectedValue);
});
it.skip('get value for mapping with fixed-size byte array keys', async () => {
const mapKey = ethers.utils.hexlify(ethers.utils.randomBytes(8));
const [, signer1] = await ethers.getSigners();
await testMappingTypes.setBytesAddressMap(mapKey, signer1.address);
const blockHash = await getBlockHash();
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testMappingTypes.address, 'bytesAddressMap', mapKey);
expect(value).to.equal(signer1.address);
});
it('get value for mapping with enum type keys', async () => {
const mapKey = 1;
const expectedValue = 123;
await testMappingTypes.setEnumIntMap(mapKey, expectedValue);
const blockHash = await getBlockHash();
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testMappingTypes.address, 'enumIntMap', mapKey);
expect(value).to.equal(BigInt(expectedValue));
});
it('get value for mapping with string type keys', async () => {
const mapKey = 'abc';
const expectedValue = 123;
await testMappingTypes.setStringIntMap(mapKey, expectedValue);
const blockHash = await getBlockHash();
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testMappingTypes.address, 'stringIntMap', mapKey);
expect(value).to.equal(BigInt(expectedValue));
});
it('get value for mapping with dynamically-sized byte array as keys', async () => {
const mapKey = ethers.utils.hexlify(ethers.utils.randomBytes(64));
const expectedValue = 123;
await testMappingTypes.setBytesUintMap(mapKey, expectedValue);
const blockHash = await getBlockHash();
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testMappingTypes.address, 'bytesUintMap', mapKey);
expect(value).to.equal(BigInt(expectedValue));
});
});
describe('nested mapping type', () => {
let testNestedMapping: Contract, storageLayout: StorageLayout;
before(async () => {
const TestNestedMapping = await ethers.getContractFactory('TestNestedMapping');
testNestedMapping = await TestNestedMapping.deploy();
await testNestedMapping.deployed();
storageLayout = await getStorageLayout('TestNestedMapping');
});
it('get value for nested mapping with address type keys', async () => {
const expectedValue = 123;
const [, signer1, signer2] = await ethers.getSigners();
await testNestedMapping.connect(signer1).setNestedAddressUintMap(signer2.address, expectedValue);
const blockHash = await getBlockHash();
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testNestedMapping.address, 'nestedAddressUintMap', signer1.address, signer2.address);
expect(value).to.equal(BigInt(expectedValue));
});
it('get value for nested mapping with signed integer type keys', async () => {
const expectedValue = false;
const key = 123;
const [, signer1] = await ethers.getSigners();
await testNestedMapping.setIntAddressBoolMap(key, signer1.address, expectedValue);
const blockHash = await getBlockHash();
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testNestedMapping.address, 'intAddressBoolMap', key, signer1.address);
expect(value).to.equal(expectedValue);
});
it('get value for nested mapping with unsigned integer type keys', async () => {
const expectedValue = 123;
const key = 456;
const nestedKey = 'abc';
await testNestedMapping.setUintStringIntMap(key, nestedKey, expectedValue);
const blockHash = await getBlockHash();
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testNestedMapping.address, 'uintStringIntMap', key, nestedKey);
expect(value).to.equal(BigInt(expectedValue));
});
it('get value for nested mapping with dynamically-sized byte array as keys', async () => {
const key = ethers.utils.hexlify(ethers.utils.randomBytes(64));
const nestedKey = 123;
const [, signer1] = await ethers.getSigners();
await testNestedMapping.setBytesIntAddressMap(key, nestedKey, signer1.address);
const blockHash = await getBlockHash();
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testNestedMapping.address, 'bytesIntAddressMap', key, nestedKey);
expect(value).to.equal(signer1.address.toLowerCase());
});
it('get value for nested mapping with string type keys', async () => {
const key = 'abc';
const expectedValue = 123;
const [, signer1] = await ethers.getSigners();
await testNestedMapping.setStringAddressIntMap(key, signer1.address, expectedValue);
const blockHash = await getBlockHash();
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testNestedMapping.address, 'stringAddressIntMap', key, signer1.address);
expect(value).to.equal(BigInt(expectedValue));
});
});
});