mirror of
https://github.com/cerc-io/watcher-ts
synced 2024-11-19 20:36:19 +00:00
Tests for getting array and string type member value in struct (#57)
* Add tests for structs with fixed array and string type members. * Refactor functions in solidity-mapper storage. * Add tests for getting array and string type member value in struct. Co-authored-by: nikugogoi <95nikass@gmail.com>
This commit is contained in:
parent
2fcfadecc2
commit
f5c1a22fdc
@ -376,8 +376,8 @@ describe('Get value from storage', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('structs type', () => {
|
||||
let testStructs: Contract, storageLayout: StorageLayout;
|
||||
describe('structs with value type members', () => {
|
||||
let testValueStructs: Contract, storageLayout: StorageLayout;
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
let addressStruct: { [key: string]: any }, contractStruct: { [key: string]: any };
|
||||
|
||||
@ -400,10 +400,10 @@ describe('Get value from storage', () => {
|
||||
};
|
||||
|
||||
before(async () => {
|
||||
const TestStructs = await ethers.getContractFactory('TestStructs');
|
||||
testStructs = await TestStructs.deploy();
|
||||
await testStructs.deployed();
|
||||
storageLayout = await getStorageLayout('TestStructs');
|
||||
const TestValueStructs = await ethers.getContractFactory('TestValueStructs');
|
||||
testValueStructs = await TestValueStructs.deploy();
|
||||
await testValueStructs.deployed();
|
||||
storageLayout = await getStorageLayout('TestValueStructs');
|
||||
|
||||
const [signer1, signer2] = await ethers.getSigners();
|
||||
|
||||
@ -431,54 +431,54 @@ describe('Get value from storage', () => {
|
||||
uint1: BigInt(4)
|
||||
};
|
||||
|
||||
await testStructs.setSingleSlotStruct(expectedValue.int1, expectedValue.uint1);
|
||||
await testValueStructs.setSingleSlotStruct(expectedValue.int1, expectedValue.uint1);
|
||||
const blockHash = await getBlockHash();
|
||||
const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testStructs.address, 'singleSlotStruct');
|
||||
const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testValueStructs.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 () => {
|
||||
await testStructs.setMultipleSlotStruct(multipleSlotStruct.uint1, multipleSlotStruct.bool1, multipleSlotStruct.int1);
|
||||
await testValueStructs.setMultipleSlotStruct(multipleSlotStruct.uint1, multipleSlotStruct.bool1, multipleSlotStruct.int1);
|
||||
const blockHash = await getBlockHash();
|
||||
const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testStructs.address, 'multipleSlotStruct');
|
||||
const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testValueStructs.address, 'multipleSlotStruct');
|
||||
expect(value).to.eql(multipleSlotStruct);
|
||||
const proofData = JSON.parse(proof.data);
|
||||
expect(proofData).to.have.all.keys(Object.keys(multipleSlotStruct));
|
||||
});
|
||||
|
||||
it('get value for struct with address type members', async () => {
|
||||
await testStructs.setAddressStruct(addressStruct.int1, addressStruct.address1, addressStruct.address2, addressStruct.uint1);
|
||||
await testValueStructs.setAddressStruct(addressStruct.int1, addressStruct.address1, addressStruct.address2, addressStruct.uint1);
|
||||
const blockHash = await getBlockHash();
|
||||
const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testStructs.address, 'addressStruct');
|
||||
const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testValueStructs.address, 'addressStruct');
|
||||
expect(value).to.eql(addressStruct);
|
||||
const proofData = JSON.parse(proof.data);
|
||||
expect(proofData).to.have.all.keys(Object.keys(addressStruct));
|
||||
});
|
||||
|
||||
it('get value for struct with contract type members', async () => {
|
||||
await testStructs.setContractStruct(contractStruct.uint1, contractStruct.testContract);
|
||||
await testValueStructs.setContractStruct(contractStruct.uint1, contractStruct.testContract);
|
||||
const blockHash = await getBlockHash();
|
||||
const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testStructs.address, 'contractStruct');
|
||||
const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testValueStructs.address, 'contractStruct');
|
||||
expect(value).to.eql(contractStruct);
|
||||
const proofData = JSON.parse(proof.data);
|
||||
expect(proofData).to.have.all.keys(Object.keys(contractStruct));
|
||||
});
|
||||
|
||||
it('get value for struct with fixed-sized byte array members', async () => {
|
||||
await testStructs.setFixedBytesStruct(fixedBytesStruct.uint1, fixedBytesStruct.bytesTen, fixedBytesStruct.bytesTwenty);
|
||||
await testValueStructs.setFixedBytesStruct(fixedBytesStruct.uint1, fixedBytesStruct.bytesTen, fixedBytesStruct.bytesTwenty);
|
||||
const blockHash = await getBlockHash();
|
||||
const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testStructs.address, 'fixedBytesStruct');
|
||||
const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testValueStructs.address, 'fixedBytesStruct');
|
||||
expect(value).to.eql(fixedBytesStruct);
|
||||
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 () => {
|
||||
await testStructs.setEnumStruct(enumStruct.uint1, enumStruct.choice1, enumStruct.choice2);
|
||||
await testValueStructs.setEnumStruct(enumStruct.uint1, enumStruct.choice1, enumStruct.choice2);
|
||||
const blockHash = await getBlockHash();
|
||||
const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testStructs.address, 'enumStruct');
|
||||
const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testValueStructs.address, 'enumStruct');
|
||||
expect(value).to.eql(enumStruct);
|
||||
const proofData = JSON.parse(proof.data);
|
||||
expect(proofData).to.have.all.keys('uint1', 'choice1', 'choice2');
|
||||
@ -487,59 +487,153 @@ describe('Get value from storage', () => {
|
||||
// Get value of a member in a struct
|
||||
it('get value of signed integer type member in a struct', async () => {
|
||||
const member = 'int1';
|
||||
await testStructs.setMultipleSlotStruct(multipleSlotStruct.uint1, multipleSlotStruct.bool1, multipleSlotStruct.int1);
|
||||
await testValueStructs.setMultipleSlotStruct(multipleSlotStruct.uint1, multipleSlotStruct.bool1, multipleSlotStruct.int1);
|
||||
const blockHash = await getBlockHash();
|
||||
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testStructs.address, 'multipleSlotStruct', member);
|
||||
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testValueStructs.address, 'multipleSlotStruct', member);
|
||||
expect(value).to.equal(multipleSlotStruct[member]);
|
||||
});
|
||||
|
||||
it('get value of unsigned integer type member in a struct', async () => {
|
||||
const member = 'uint1';
|
||||
const blockHash = await getBlockHash();
|
||||
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testStructs.address, 'multipleSlotStruct', member);
|
||||
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testValueStructs.address, 'multipleSlotStruct', member);
|
||||
expect(value).to.equal(multipleSlotStruct[member]);
|
||||
});
|
||||
|
||||
it('get value of boolean type member in a struct', async () => {
|
||||
const member = 'bool1';
|
||||
const blockHash = await getBlockHash();
|
||||
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testStructs.address, 'multipleSlotStruct', member);
|
||||
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testValueStructs.address, 'multipleSlotStruct', member);
|
||||
expect(value).to.equal(multipleSlotStruct[member]);
|
||||
});
|
||||
|
||||
it('get value of address type member in a struct', async () => {
|
||||
const member = 'address1';
|
||||
await testStructs.setAddressStruct(addressStruct.int1, addressStruct.address1, addressStruct.address2, addressStruct.uint1);
|
||||
await testValueStructs.setAddressStruct(addressStruct.int1, addressStruct.address1, addressStruct.address2, addressStruct.uint1);
|
||||
const blockHash = await getBlockHash();
|
||||
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testStructs.address, 'addressStruct', member);
|
||||
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testValueStructs.address, 'addressStruct', member);
|
||||
expect(value).to.equal(addressStruct[member]);
|
||||
});
|
||||
|
||||
it('get value of contract type member in a struct', async () => {
|
||||
const member = 'testContract';
|
||||
await testStructs.setContractStruct(contractStruct.uint1, contractStruct.testContract);
|
||||
await testValueStructs.setContractStruct(contractStruct.uint1, contractStruct.testContract);
|
||||
const blockHash = await getBlockHash();
|
||||
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testStructs.address, 'contractStruct', member);
|
||||
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testValueStructs.address, 'contractStruct', member);
|
||||
expect(value).to.equal(contractStruct[member]);
|
||||
});
|
||||
|
||||
it('get value of fixed byte array member in a struct', async () => {
|
||||
const member = 'bytesTen';
|
||||
await testStructs.setFixedBytesStruct(fixedBytesStruct.uint1, fixedBytesStruct.bytesTen, fixedBytesStruct.bytesTwenty);
|
||||
await testValueStructs.setFixedBytesStruct(fixedBytesStruct.uint1, fixedBytesStruct.bytesTen, fixedBytesStruct.bytesTwenty);
|
||||
const blockHash = await getBlockHash();
|
||||
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testStructs.address, 'fixedBytesStruct', member);
|
||||
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testValueStructs.address, 'fixedBytesStruct', member);
|
||||
expect(value).to.equal(fixedBytesStruct[member]);
|
||||
});
|
||||
|
||||
it('get value of enum type member in a struct', async () => {
|
||||
const member = 'choice2';
|
||||
await testStructs.setEnumStruct(enumStruct.uint1, enumStruct.choice1, enumStruct.choice2);
|
||||
await testValueStructs.setEnumStruct(enumStruct.uint1, enumStruct.choice1, enumStruct.choice2);
|
||||
const blockHash = await getBlockHash();
|
||||
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testStructs.address, 'enumStruct', member);
|
||||
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testValueStructs.address, 'enumStruct', member);
|
||||
expect(value).to.equal(enumStruct[member]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('structs with reference type members', () => {
|
||||
let testReferenceStructs: Contract, storageLayout: StorageLayout;
|
||||
let fixedArrayStruct: {[key: string]: any};
|
||||
|
||||
const stringStruct = {
|
||||
string1: 'string1',
|
||||
uint1: BigInt(123),
|
||||
string2: 'string2'
|
||||
};
|
||||
|
||||
before(async () => {
|
||||
const TestReferenceStructs = await ethers.getContractFactory('TestReferenceStructs');
|
||||
testReferenceStructs = await TestReferenceStructs.deploy();
|
||||
await testReferenceStructs.deployed();
|
||||
storageLayout = await getStorageLayout('TestReferenceStructs');
|
||||
|
||||
const signers = await ethers.getSigners();
|
||||
|
||||
fixedArrayStruct = {
|
||||
int1: BigInt(123),
|
||||
uintArray: [1, 2, 3, 4].map(el => BigInt(el)),
|
||||
addressArray: signers.slice(0, 3).map(signer => signer.address.toLowerCase())
|
||||
};
|
||||
});
|
||||
|
||||
// Get all members of a struct.
|
||||
it('get value for struct with fixed-size array members', async () => {
|
||||
await testReferenceStructs.setFixedArrayStruct(fixedArrayStruct.int1, fixedArrayStruct.uintArray, fixedArrayStruct.addressArray);
|
||||
const blockHash = await getBlockHash();
|
||||
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testReferenceStructs.address, 'fixedArrayStruct');
|
||||
expect(value).to.eql(fixedArrayStruct);
|
||||
});
|
||||
|
||||
it('get value for struct with string type members', async () => {
|
||||
await testReferenceStructs.setStringStruct(stringStruct.string1, stringStruct.uint1, stringStruct.string2);
|
||||
const blockHash = await getBlockHash();
|
||||
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testReferenceStructs.address, 'stringStruct');
|
||||
expect(value).to.eql(stringStruct);
|
||||
});
|
||||
|
||||
// Get value of a member in a struct
|
||||
it('get value of fixed-size array member in a struct', async () => {
|
||||
const member = 'uintArray';
|
||||
await testReferenceStructs.setFixedArrayStruct(fixedArrayStruct.int1, fixedArrayStruct.uintArray, fixedArrayStruct.addressArray);
|
||||
const blockHash = await getBlockHash();
|
||||
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testReferenceStructs.address, 'fixedArrayStruct', member);
|
||||
expect(value).to.eql(fixedArrayStruct[member]);
|
||||
});
|
||||
|
||||
it('get value of string member in a struct', async () => {
|
||||
const member = 'string2';
|
||||
await testReferenceStructs.setStringStruct(stringStruct.string1, stringStruct.uint1, stringStruct.string2);
|
||||
const blockHash = await getBlockHash();
|
||||
const { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testReferenceStructs.address, 'stringStruct', member);
|
||||
expect(value).to.eql(stringStruct[member]);
|
||||
});
|
||||
|
||||
it.skip('get value of mapping type member in a struct', async () => {
|
||||
const [signer1, signer2] = await ethers.getSigners();
|
||||
|
||||
const valueMappingStruct: { [key: string]: any } = {
|
||||
uintAddressMap: new Map(),
|
||||
uint1: 123,
|
||||
addressIntMap: new Map()
|
||||
};
|
||||
|
||||
const mappingKey = 456;
|
||||
valueMappingStruct.uintAddressMap.set(mappingKey, signer1.address.toLowerCase());
|
||||
valueMappingStruct.addressIntMap.set(signer2.address, 789);
|
||||
let member = 'uintAddressMap';
|
||||
|
||||
await testReferenceStructs.setValueMappingStruct(mappingKey, valueMappingStruct.uintAddressMap.get(mappingKey), valueMappingStruct.uint1, signer2.address, valueMappingStruct.addressIntMap.get(signer2.address));
|
||||
let blockHash = await getBlockHash();
|
||||
let { value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testReferenceStructs.address, 'valueMappingStruct', member, mappingKey);
|
||||
expect(value).to.equal(valueMappingStruct[member].get(mappingKey));
|
||||
|
||||
const referenceMappingStruct: { [key: string]: any } = {
|
||||
bytesAddressMap: new Map(),
|
||||
stringUintMap: new Map()
|
||||
};
|
||||
|
||||
const bytesKey = ethers.utils.hexlify(ethers.utils.randomBytes(40));
|
||||
const stringKey = 'abc';
|
||||
referenceMappingStruct.bytesAddressMap.set(bytesKey, signer1.address.toLowerCase());
|
||||
referenceMappingStruct.stringUintMap.set(stringKey, 123);
|
||||
member = 'stringAddressMap';
|
||||
|
||||
await testReferenceStructs.setReferenceMappingStruct(bytesKey, referenceMappingStruct.bytesAddressMap.get(bytesKey), stringKey, referenceMappingStruct.stringUintMap.get(stringKey));
|
||||
blockHash = await getBlockHash();
|
||||
({ value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testReferenceStructs.address, 'referenceMappingStruct', member, stringKey));
|
||||
expect(value).to.equal(referenceMappingStruct[member].get(stringKey));
|
||||
});
|
||||
});
|
||||
|
||||
describe('basic mapping type', () => {
|
||||
let testMappingTypes: Contract, storageLayout: StorageLayout;
|
||||
|
||||
|
@ -110,90 +110,14 @@ const getDecodedValue = async (getStorageAt: GetStorageAt, blockHash: string, ad
|
||||
|
||||
// If variable is array type.
|
||||
if (isArray && base) {
|
||||
const resultArray = [];
|
||||
const proofs = [];
|
||||
let { numberOfBytes: baseNumberOfBytes, label: baseTypeLabel } = types[base];
|
||||
|
||||
// Address type elements use an entire single slot i.e. 32 bytes.
|
||||
if (baseTypeLabel === 'address' || baseTypeLabel.includes('contract')) {
|
||||
baseNumberOfBytes = '32';
|
||||
}
|
||||
|
||||
const getArrayElement = async (index: number, mappingKeys: MappingKey[]) => {
|
||||
const arrayOffset = index * Number(baseNumberOfBytes);
|
||||
const arraySlot = BigNumber.from(slot).add(Math.floor(arrayOffset / 32)).toHexString();
|
||||
const arraySlotOffset = arrayOffset % 32;
|
||||
|
||||
return getDecodedValue(getStorageAt, blockHash, address, types, { slot: arraySlot, offset: arraySlotOffset, type: base }, mappingKeys);
|
||||
};
|
||||
|
||||
const [arrayIndex, ...remainingKeys] = mappingKeys;
|
||||
|
||||
if (typeof arrayIndex === 'number') {
|
||||
return getArrayElement(arrayIndex, remainingKeys);
|
||||
}
|
||||
|
||||
// TODO: Get values in single call and parse according to type.
|
||||
// Loop over elements of array and get value.
|
||||
for (let i = 0; i < Number(arraySize); i++) {
|
||||
({ value, proof } = await getArrayElement(i, mappingKeys));
|
||||
resultArray.push(value);
|
||||
|
||||
// Each element in array gets its own proof even if it is packed.
|
||||
proofs.push(JSON.parse(proof.data));
|
||||
}
|
||||
|
||||
return {
|
||||
value: resultArray,
|
||||
proof: {
|
||||
data: JSON.stringify(proofs)
|
||||
}
|
||||
};
|
||||
return getArrayValue(getStorageAt, blockHash, address, types, mappingKeys, slot, base, Number(arraySize));
|
||||
}
|
||||
|
||||
const isStruct = /^struct .+/.test(typeLabel);
|
||||
|
||||
// If variable is struct type.
|
||||
if (isStruct && members) {
|
||||
// Get value of specified member in struct.
|
||||
const getStructMember = async (member: Storage, mappingKeys: MappingKey[]) => {
|
||||
const structSlot = BigNumber.from(slot).add(member.slot).toHexString();
|
||||
|
||||
return getDecodedValue(getStorageAt, blockHash, address, types, { slot: structSlot, offset: member.offset, type: member.type }, mappingKeys);
|
||||
};
|
||||
|
||||
const [memberName, ...remainingKeys] = mappingKeys;
|
||||
const member = members.find(member => member.label === memberName);
|
||||
|
||||
// If member name passed in argument is present.
|
||||
if (member) {
|
||||
return getStructMember(member, remainingKeys);
|
||||
}
|
||||
|
||||
// TODO: Get values in single call and parse according to type.
|
||||
// Get member values specified for the struct in storage layout.
|
||||
const resultPromises = members.map(async member => {
|
||||
return getStructMember(member, mappingKeys);
|
||||
});
|
||||
|
||||
const results = await Promise.all(resultPromises);
|
||||
|
||||
const initialValue: {
|
||||
value: {[key: string]: any},
|
||||
proof: { data: string }
|
||||
} = {
|
||||
value: {},
|
||||
proof: { data: JSON.stringify({}) }
|
||||
};
|
||||
|
||||
// Return struct type value as an object with keys as the struct member labels.
|
||||
return members.reduce((acc, member, index) => {
|
||||
acc.value[member.label] = results[index].value;
|
||||
const proofData = JSON.parse(acc.proof.data);
|
||||
proofData[member.label] = results[index].proof;
|
||||
acc.proof.data = JSON.stringify(proofData);
|
||||
return acc;
|
||||
}, initialValue);
|
||||
return getStructureValue(getStorageAt, blockHash, address, types, mappingKeys, slot, members);
|
||||
}
|
||||
|
||||
// Get value according to encoding i.e. how the data is encoded in storage.
|
||||
@ -201,17 +125,17 @@ const getDecodedValue = async (getStorageAt: GetStorageAt, blockHash: string, ad
|
||||
switch (encoding) {
|
||||
// https://docs.soliditylang.org/en/v0.8.4/internals/layout_in_storage.html#layout-of-state-variables-in-storage
|
||||
case 'inplace':
|
||||
({ value, proof } = await getInplaceValue(blockHash, address, slot, offset, numberOfBytes, getStorageAt));
|
||||
({ value, proof } = await getInplaceValue(getStorageAt, blockHash, address, slot, offset, numberOfBytes));
|
||||
break;
|
||||
|
||||
// https://docs.soliditylang.org/en/v0.8.4/internals/layout_in_storage.html#bytes-and-string
|
||||
case 'bytes':
|
||||
({ value, proof } = await getBytesValue(blockHash, address, slot, getStorageAt));
|
||||
({ value, proof } = await getBytesValue(getStorageAt, blockHash, address, slot));
|
||||
break;
|
||||
|
||||
case 'mapping': {
|
||||
if (mappingValueType && mappingKeyType) {
|
||||
const mappingSlot = getMappingSlot(slot, types, mappingKeyType, mappingKeys[0]);
|
||||
const mappingSlot = getMappingSlot(types, slot, mappingKeyType, mappingKeys[0]);
|
||||
|
||||
return getDecodedValue(getStorageAt, blockHash, address, types, { slot: mappingSlot, offset: 0, type: mappingValueType }, mappingKeys.slice(1));
|
||||
} else {
|
||||
@ -236,7 +160,7 @@ const getDecodedValue = async (getStorageAt: GetStorageAt, blockHash: string, ad
|
||||
* @param mappingSlot
|
||||
* @param key
|
||||
*/
|
||||
export const getMappingSlot = (mappingSlot: string, types: Types, keyType: string, key: MappingKey): string => {
|
||||
export const getMappingSlot = (types: Types, mappingSlot: string, keyType: string, key: MappingKey): string => {
|
||||
const { encoding, label: typeLabel } = types[keyType];
|
||||
|
||||
// If key is boolean type convert to 1 or 0 which is the way value is stored in memory.
|
||||
@ -245,7 +169,7 @@ export const getMappingSlot = (mappingSlot: string, types: Types, keyType: strin
|
||||
}
|
||||
|
||||
// If key is string convert to hex string representation.
|
||||
if (typeLabel.includes('string') && typeof key === 'string') {
|
||||
if (typeLabel === 'string' && typeof key === 'string') {
|
||||
key = utils.hexlify(utils.toUtf8Bytes(key));
|
||||
}
|
||||
|
||||
@ -271,6 +195,90 @@ export const getMappingSlot = (mappingSlot: string, types: Types, keyType: strin
|
||||
return slot;
|
||||
};
|
||||
|
||||
const getArrayValue = async (getStorageAt: GetStorageAt, blockHash: string, address: string, types: Types, mappingKeys: MappingKey[], slot: string, base: string, arraySize: number) => {
|
||||
const resultArray = [];
|
||||
const proofs = [];
|
||||
let { numberOfBytes: baseNumberOfBytes, label: baseTypeLabel } = types[base];
|
||||
|
||||
// Address type elements use an entire single slot i.e. 32 bytes.
|
||||
if (baseTypeLabel === 'address' || baseTypeLabel.includes('contract')) {
|
||||
baseNumberOfBytes = '32';
|
||||
}
|
||||
|
||||
const getArrayElement = async (mappingKeys: MappingKey[], index: number) => {
|
||||
const arrayOffset = index * Number(baseNumberOfBytes);
|
||||
const arraySlot = BigNumber.from(slot).add(Math.floor(arrayOffset / 32)).toHexString();
|
||||
const arraySlotOffset = arrayOffset % 32;
|
||||
|
||||
return getDecodedValue(getStorageAt, blockHash, address, types, { slot: arraySlot, offset: arraySlotOffset, type: base }, mappingKeys);
|
||||
};
|
||||
|
||||
const [arrayIndex, ...remainingKeys] = mappingKeys;
|
||||
|
||||
if (typeof arrayIndex === 'number') {
|
||||
return getArrayElement(remainingKeys, arrayIndex);
|
||||
}
|
||||
|
||||
// TODO: Get values in single call and parse according to type.
|
||||
// Loop over elements of array and get value.
|
||||
for (let i = 0; i < arraySize; i++) {
|
||||
const { value, proof } = await getArrayElement(mappingKeys, i);
|
||||
resultArray.push(value);
|
||||
|
||||
// Each element in array gets its own proof even if it is packed.
|
||||
proofs.push(JSON.parse(proof.data));
|
||||
}
|
||||
|
||||
return {
|
||||
value: resultArray,
|
||||
proof: {
|
||||
data: JSON.stringify(proofs)
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const getStructureValue = async (getStorageAt: GetStorageAt, blockHash: string, address: string, types: Types, mappingKeys: MappingKey[], slot: string, members: Storage[]) => {
|
||||
// Get value of specified member in struct.
|
||||
const getStructMember = async (mappingKeys: MappingKey[], member: Storage) => {
|
||||
const structSlot = BigNumber.from(slot).add(member.slot).toHexString();
|
||||
|
||||
return getDecodedValue(getStorageAt, blockHash, address, types, { slot: structSlot, offset: member.offset, type: member.type }, mappingKeys);
|
||||
};
|
||||
|
||||
const [memberName, ...remainingKeys] = mappingKeys;
|
||||
const member = members.find(member => member.label === memberName);
|
||||
|
||||
// If member name passed in argument is present.
|
||||
if (member) {
|
||||
return getStructMember(remainingKeys, member);
|
||||
}
|
||||
|
||||
// TODO: Get values in single call and parse according to type.
|
||||
// Get member values specified for the struct in storage layout.
|
||||
const resultPromises = members.map(async member => {
|
||||
return getStructMember(mappingKeys, member);
|
||||
});
|
||||
|
||||
const results = await Promise.all(resultPromises);
|
||||
|
||||
const initialValue: {
|
||||
value: {[key: string]: any},
|
||||
proof: { data: string }
|
||||
} = {
|
||||
value: {},
|
||||
proof: { data: JSON.stringify({}) }
|
||||
};
|
||||
|
||||
// Return struct type value as an object with keys as the struct member labels.
|
||||
return members.reduce((acc, member, index) => {
|
||||
acc.value[member.label] = results[index].value;
|
||||
const proofData = JSON.parse(acc.proof.data);
|
||||
proofData[member.label] = results[index].proof;
|
||||
acc.proof.data = JSON.stringify(proofData);
|
||||
return acc;
|
||||
}, initialValue);
|
||||
};
|
||||
|
||||
/**
|
||||
* Function to get value for inplace encoding.
|
||||
* @param address
|
||||
@ -279,7 +287,7 @@ export const getMappingSlot = (mappingSlot: string, types: Types, keyType: strin
|
||||
* @param numberOfBytes
|
||||
* @param getStorageAt
|
||||
*/
|
||||
const getInplaceValue = async (blockHash: string, address: string, slot: string, offset: number, numberOfBytes: string, getStorageAt: GetStorageAt) => {
|
||||
const getInplaceValue = async (getStorageAt: GetStorageAt, blockHash: string, address: string, slot: string, offset: number, numberOfBytes: string) => {
|
||||
const { value, proof } = await getStorageAt({ blockHash, contract: address, slot });
|
||||
const valueLength = utils.hexDataLength(value);
|
||||
|
||||
@ -299,7 +307,7 @@ const getInplaceValue = async (blockHash: string, address: string, slot: string,
|
||||
* @param slot
|
||||
* @param getStorageAt
|
||||
*/
|
||||
const getBytesValue = async (blockHash: string, address: string, slot: string, getStorageAt: GetStorageAt) => {
|
||||
const getBytesValue = async (getStorageAt: GetStorageAt, blockHash: string, address: string, slot: string) => {
|
||||
const { value, proof } = await getStorageAt({ blockHash, contract: address, slot });
|
||||
let length = 0;
|
||||
|
||||
|
@ -0,0 +1,62 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
contract TestReferenceStructs {
|
||||
struct FixedArrayStruct {
|
||||
int8 int1;
|
||||
uint16[4] uintArray;
|
||||
address[3] addressArray;
|
||||
}
|
||||
|
||||
FixedArrayStruct fixedArrayStruct;
|
||||
|
||||
struct StringStruct {
|
||||
string string1;
|
||||
uint8 uint1;
|
||||
string string2;
|
||||
}
|
||||
|
||||
StringStruct stringStruct;
|
||||
|
||||
struct ValueMappingStruct {
|
||||
mapping(uint => address) uintAddressMap;
|
||||
uint32 uint1;
|
||||
mapping(address => int) addressIntMap;
|
||||
}
|
||||
|
||||
ValueMappingStruct public valueMappingStruct;
|
||||
|
||||
struct ReferenceMappingStruct {
|
||||
mapping(bytes => address) bytesAddressMap;
|
||||
mapping(string => uint) stringUintMap;
|
||||
}
|
||||
|
||||
ReferenceMappingStruct referenceMappingStruct;
|
||||
|
||||
// Set variable fixedArrayStruct.
|
||||
function setFixedArrayStruct(int8 int1Value, uint16[4] calldata uintArrayValue, address[3] calldata addressArrayValue) external {
|
||||
fixedArrayStruct.int1 = int1Value;
|
||||
fixedArrayStruct.uintArray = uintArrayValue;
|
||||
fixedArrayStruct.addressArray = addressArrayValue;
|
||||
}
|
||||
|
||||
// Set variable stringStruct.
|
||||
function setStringStruct(string calldata string1Value, uint8 uint1Value, string calldata string2Value) external {
|
||||
stringStruct.string1 = string1Value;
|
||||
stringStruct.uint1 = uint1Value;
|
||||
stringStruct.string2 = string2Value;
|
||||
}
|
||||
|
||||
// Set variable valueMappingStruct.
|
||||
function setValueMappingStruct(uint uintAddressKey, address uintAddressValue, uint32 uint1Value, address addressIntKey, int addressIntValue) external {
|
||||
valueMappingStruct.uintAddressMap[uintAddressKey] = uintAddressValue;
|
||||
valueMappingStruct.uint1 = uint1Value;
|
||||
valueMappingStruct.addressIntMap[addressIntKey] = addressIntValue;
|
||||
}
|
||||
|
||||
// Set variable referenceMappingStruct.
|
||||
function setReferenceMappingStruct(bytes calldata bytesAddressKey, address bytesAddressValue, string calldata stringUintKey, uint stringUintValue) external {
|
||||
referenceMappingStruct.bytesAddressMap[bytesAddressKey] = bytesAddressValue;
|
||||
referenceMappingStruct.stringUintMap[stringUintKey] = stringUintValue;
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ pragma solidity ^0.7.0;
|
||||
|
||||
import "./TestContractTypes.sol";
|
||||
|
||||
contract TestStructs {
|
||||
contract TestValueStructs {
|
||||
struct SingleSlotStruct {
|
||||
int16 int1;
|
||||
uint8 uint1;
|
@ -62,6 +62,8 @@ export const getStorageAt: GetStorageAt = async ({ blockHash, contract, slot })
|
||||
return {
|
||||
value,
|
||||
proof: {
|
||||
// Returning null value as proof, since ethers library getStorageAt method doesnt return proof.
|
||||
// This function is used in tests to mock the getStorageAt method of ipld-eth-client which returns proof along with value.
|
||||
data: JSON.stringify(null)
|
||||
}
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user