mirror of
https://github.com/cerc-io/watcher-ts
synced 2024-11-19 20:36:19 +00:00
Get value of struct type (#54)
* Implement getting value for struct types. * Add tests for structs with value type memebers. * Add tests for verifying proof in struct type. Co-authored-by: nikugogoi <95nikass@gmail.com>
This commit is contained in:
parent
5316b19fbf
commit
8851882144
@ -55,7 +55,9 @@ $ yarn test
|
||||
* [ ] Bytes
|
||||
* [x] String
|
||||
* [ ] Structs
|
||||
* [ ] Get struct value with all members
|
||||
* [ ] Value Types
|
||||
* [ ] Get value of a single member in struct
|
||||
* [ ] Reference Types
|
||||
* [ ] Mapping Types
|
||||
* [x] Value Type keys
|
||||
|
@ -343,6 +343,111 @@ describe('Get value from storage', () => {
|
||||
});
|
||||
});
|
||||
|
||||
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;
|
||||
|
||||
|
@ -15,6 +15,7 @@ interface Types {
|
||||
base?: string;
|
||||
value?: string;
|
||||
key?: string;
|
||||
members?: Storage[];
|
||||
};
|
||||
}
|
||||
|
||||
@ -102,7 +103,7 @@ export const getValueByType = (storageValue: string, typeLabel: string): bigint
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
const getDecodedValue = async (getStorageAt: GetStorageAt, blockHash: string, address: string, types: Types, storageInfo: { slot: string, offset: number, type: string }, mappingKeys: Array<MappingKey>): Promise<{ value: any, proof: { data: string } }> => {
|
||||
const { slot, offset, type } = storageInfo;
|
||||
const { encoding, numberOfBytes, label: typeLabel, base, value: mappingValueType, key: mappingKeyType } = types[type];
|
||||
const { encoding, numberOfBytes, label: typeLabel, base, value: mappingValueType, key: mappingKeyType, members } = types[type];
|
||||
|
||||
const [isArray, arraySize] = typeLabel.match(/\[([0-9]*)\]/) || [false];
|
||||
let value: string, proof: { data: string };
|
||||
@ -138,6 +139,38 @@ const getDecodedValue = async (getStorageAt: GetStorageAt, blockHash: string, ad
|
||||
};
|
||||
}
|
||||
|
||||
const isStruct = /^struct .+/.test(typeLabel);
|
||||
|
||||
// If variable is struct type.
|
||||
if (isStruct && members) {
|
||||
// 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 => {
|
||||
const structSlot = BigNumber.from(slot).add(member.slot).toHexString();
|
||||
|
||||
return getDecodedValue(getStorageAt, blockHash, address, types, { slot: structSlot, offset: member.offset, type: member.type }, []);
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 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
|
||||
switch (encoding) {
|
||||
@ -153,7 +186,7 @@ const getDecodedValue = async (getStorageAt: GetStorageAt, blockHash: string, ad
|
||||
|
||||
case 'mapping': {
|
||||
if (mappingValueType && mappingKeyType) {
|
||||
const mappingSlot = await getMappingSlot(slot, types, mappingKeyType, mappingKeys[0]);
|
||||
const mappingSlot = getMappingSlot(slot, types, mappingKeyType, mappingKeys[0]);
|
||||
|
||||
return getDecodedValue(getStorageAt, blockHash, address, types, { slot: mappingSlot, offset: 0, type: mappingValueType }, mappingKeys.slice(1));
|
||||
} else {
|
||||
|
98
packages/solidity-mapper/test/contracts/TestStructs.sol
Normal file
98
packages/solidity-mapper/test/contracts/TestStructs.sol
Normal file
@ -0,0 +1,98 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
import "./TestContractTypes.sol";
|
||||
|
||||
contract TestStructs {
|
||||
struct SingleSlotStruct {
|
||||
int16 int1;
|
||||
uint8 uint1;
|
||||
}
|
||||
|
||||
// Struct variable will use one single slot as size of the members is less than 32 bytes.
|
||||
SingleSlotStruct singleSlotStruct;
|
||||
|
||||
struct MultipleSlotStruct {
|
||||
uint128 uint1;
|
||||
bool bool1;
|
||||
int192 int1;
|
||||
}
|
||||
|
||||
// Struct variable will use multiple slots as size of the members is more than 32 bytes.
|
||||
MultipleSlotStruct multipleSlotStruct;
|
||||
|
||||
struct AddressStruct {
|
||||
int8 int1;
|
||||
address address1;
|
||||
address address2;
|
||||
uint16 uint1;
|
||||
}
|
||||
|
||||
AddressStruct addressStruct;
|
||||
|
||||
struct ContractStruct {
|
||||
uint16 uint1;
|
||||
TestContractTypes testContract;
|
||||
}
|
||||
|
||||
ContractStruct contractStruct;
|
||||
|
||||
struct FixedBytesStruct {
|
||||
uint8 uint1;
|
||||
bytes10 bytesTen;
|
||||
bytes20 bytesTwenty;
|
||||
}
|
||||
|
||||
FixedBytesStruct fixedBytesStruct;
|
||||
|
||||
enum Choices { Choice0, Choice1, Choice2, Choice3 }
|
||||
|
||||
struct EnumStruct {
|
||||
uint32 uint1;
|
||||
Choices choice1;
|
||||
Choices choice2;
|
||||
}
|
||||
|
||||
EnumStruct enumStruct;
|
||||
|
||||
// Set variable singleSlotStruct.
|
||||
function setSingleSlotStruct(int16 int1Value, uint8 uint1Value) external {
|
||||
singleSlotStruct.int1 = int1Value;
|
||||
singleSlotStruct.uint1 = uint1Value;
|
||||
}
|
||||
|
||||
// Set variable multipleSlotStruct.
|
||||
function setMultipleSlotStruct(uint128 uint1Value, bool bool1Value, int192 int1Value) external {
|
||||
multipleSlotStruct.uint1 = uint1Value;
|
||||
multipleSlotStruct.bool1 = bool1Value;
|
||||
multipleSlotStruct.int1 = int1Value;
|
||||
}
|
||||
|
||||
// Set variable addressStruct.
|
||||
function setAddressStruct(int8 int1Value, address address1Value, address address2Value, uint16 uint1Value) external {
|
||||
addressStruct.int1 = int1Value;
|
||||
addressStruct.address1 = address1Value;
|
||||
addressStruct.address2 = address2Value;
|
||||
addressStruct.uint1 = uint1Value;
|
||||
}
|
||||
|
||||
// Set variable contractStruct.
|
||||
function setContractStruct(uint16 uint1Value, TestContractTypes contractValue) external {
|
||||
contractStruct.uint1 = uint1Value;
|
||||
contractStruct.testContract = contractValue;
|
||||
}
|
||||
|
||||
// Set variable fixedBytesStruct.
|
||||
function setFixedBytesStruct(uint8 uint1Value, bytes10 bytesTenValue, bytes20 bytesTwentyValue) external {
|
||||
fixedBytesStruct.uint1 = uint1Value;
|
||||
fixedBytesStruct.bytesTen = bytesTenValue;
|
||||
fixedBytesStruct.bytesTwenty = bytesTwentyValue;
|
||||
}
|
||||
|
||||
// Set variable enumStruct.
|
||||
function setEnumStruct(uint32 uint1Value, Choices choice1Value, Choices choice2Value) external {
|
||||
enumStruct.uint1 = uint1Value;
|
||||
enumStruct.choice1 = choice1Value;
|
||||
enumStruct.choice2 = choice2Value;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user