mirror of
https://github.com/cerc-io/watcher-ts
synced 2025-02-03 00:32:49 +00:00
Get value for dynamic arrays (#66)
* Implement getting values of dynamic arrays. * Add tests for dynamic arrays of value types. Co-authored-by: nikugogoi <95nikass@gmail.com>
This commit is contained in:
parent
3439dd4041
commit
d4db1f5d28
@ -40,11 +40,11 @@ $ yarn test
|
|||||||
* [x] Struct Type
|
* [x] Struct Type
|
||||||
* [ ] Mapping Type
|
* [ ] Mapping Type
|
||||||
* [ ] Dynamically-sized arrays
|
* [ ] Dynamically-sized arrays
|
||||||
* [ ] Integer Type
|
* [x] Integer Type
|
||||||
* [ ] Boolean Type
|
* [x] Boolean Type
|
||||||
* [ ] Address Type
|
* [x] Address Type
|
||||||
* [ ] Fixed-size byte arrays
|
* [ ] Fixed-size byte arrays
|
||||||
* [ ] Enum Type
|
* [x] Enum Type
|
||||||
* [ ] Dynamically-sized byte array
|
* [ ] Dynamically-sized byte array
|
||||||
* [ ] Struct Type
|
* [ ] Struct Type
|
||||||
* [ ] Mapping Type
|
* [ ] Mapping Type
|
||||||
|
@ -417,6 +417,109 @@ describe('Get value from storage', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('dynamic sized arrays', () => {
|
||||||
|
let testDynamicArrays: Contract, storageLayout: StorageLayout;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
const TestFixedArrays = await ethers.getContractFactory('TestDynamicArrays');
|
||||||
|
testDynamicArrays = await TestFixedArrays.deploy();
|
||||||
|
await testDynamicArrays.deployed();
|
||||||
|
storageLayout = await getStorageLayout('TestDynamicArrays');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get all elements of array.
|
||||||
|
it('get value for dynamic sized array of boolean type', async () => {
|
||||||
|
const boolArray = [true, false, false, true, false];
|
||||||
|
await testDynamicArrays.setBoolArray(boolArray);
|
||||||
|
const blockHash = await getBlockHash();
|
||||||
|
let { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testDynamicArrays.address, 'boolArray');
|
||||||
|
expect(value).to.eql(boolArray);
|
||||||
|
const proofData = JSON.parse(proof.data);
|
||||||
|
expect(proofData.length).to.equal(boolArray.length);
|
||||||
|
|
||||||
|
// Get value by index
|
||||||
|
const arrayIndex = 2;
|
||||||
|
({ value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testDynamicArrays.address, 'boolArray', arrayIndex));
|
||||||
|
expect(value).to.equal(boolArray[arrayIndex]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('get value for dynamic sized array of unsigned integer type', async () => {
|
||||||
|
const uint128Array = [100, 200, 300, 400, 500];
|
||||||
|
await testDynamicArrays.setUintArray(uint128Array);
|
||||||
|
const blockHash = await getBlockHash();
|
||||||
|
let { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testDynamicArrays.address, 'uintArray');
|
||||||
|
expect(value).to.eql(uint128Array.map(el => BigInt(el)));
|
||||||
|
const proofData = JSON.parse(proof.data);
|
||||||
|
expect(proofData.length).to.equal(uint128Array.length);
|
||||||
|
|
||||||
|
// Get value by index
|
||||||
|
const arrayIndex = 3;
|
||||||
|
({ value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testDynamicArrays.address, 'uintArray', arrayIndex));
|
||||||
|
expect(value).to.equal(BigInt(uint128Array[arrayIndex]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('get value for dynamic sized array of signed integer type', async () => {
|
||||||
|
const intArray = [10, 20, 30, 40, 50];
|
||||||
|
await testDynamicArrays.setIntArray(intArray);
|
||||||
|
const blockHash = await getBlockHash();
|
||||||
|
let { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testDynamicArrays.address, 'intArray');
|
||||||
|
expect(value).to.eql(intArray.map(el => BigInt(el)));
|
||||||
|
const proofData = JSON.parse(proof.data);
|
||||||
|
expect(proofData.length).to.equal(intArray.length);
|
||||||
|
|
||||||
|
// Get value by index
|
||||||
|
const arrayIndex = 1;
|
||||||
|
({ value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testDynamicArrays.address, 'intArray', arrayIndex));
|
||||||
|
expect(value).to.equal(BigInt(intArray[arrayIndex]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('get value for dynamic sized array of address type', async () => {
|
||||||
|
const signers = await ethers.getSigners();
|
||||||
|
const addressArray = signers.map(signer => signer.address.toLowerCase());
|
||||||
|
await testDynamicArrays.setAddressArray(addressArray);
|
||||||
|
const blockHash = await getBlockHash();
|
||||||
|
let { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testDynamicArrays.address, 'addressArray');
|
||||||
|
expect(value).to.eql(addressArray);
|
||||||
|
const proofData = JSON.parse(proof.data);
|
||||||
|
expect(proofData.length).to.equal(addressArray.length);
|
||||||
|
|
||||||
|
// Get value by index
|
||||||
|
const arrayIndex = 4;
|
||||||
|
({ value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testDynamicArrays.address, 'addressArray', arrayIndex));
|
||||||
|
expect(value).to.equal(addressArray[arrayIndex]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.skip('get value for dynamic sized array of fixed size byte array', async () => {
|
||||||
|
const fixedBytesArray = Array.from({ length: 4 }, () => ethers.utils.hexlify(ethers.utils.randomBytes(10)));
|
||||||
|
await testDynamicArrays.setFixedBytesArray(fixedBytesArray);
|
||||||
|
const blockHash = await getBlockHash();
|
||||||
|
let { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testDynamicArrays.address, 'fixedBytesArray');
|
||||||
|
expect(value).to.eql(fixedBytesArray);
|
||||||
|
const proofData = JSON.parse(proof.data);
|
||||||
|
expect(proofData.length).to.equal(fixedBytesArray.length);
|
||||||
|
|
||||||
|
// Get value by index
|
||||||
|
const arrayIndex = 2;
|
||||||
|
({ value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testDynamicArrays.address, 'fixedBytesArray', arrayIndex));
|
||||||
|
expect(value).to.equal(fixedBytesArray[arrayIndex]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('get value for dynamic sized array of enum type', async () => {
|
||||||
|
const enumArray = [0, 1, 2, 3];
|
||||||
|
await testDynamicArrays.setEnumArray(enumArray);
|
||||||
|
const blockHash = await getBlockHash();
|
||||||
|
let { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testDynamicArrays.address, 'enumArray');
|
||||||
|
expect(value).to.eql(enumArray.map(el => BigInt(el)));
|
||||||
|
const proofData = JSON.parse(proof.data);
|
||||||
|
expect(proofData.length).to.equal(enumArray.length);
|
||||||
|
|
||||||
|
// Get value by index
|
||||||
|
const arrayIndex = 2;
|
||||||
|
({ value } = await getStorageValue(storageLayout, getStorageAt, blockHash, testDynamicArrays.address, 'enumArray', arrayIndex));
|
||||||
|
expect(value).to.equal(BigInt(enumArray[arrayIndex]));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('nested arrays', () => {
|
describe('nested arrays', () => {
|
||||||
let testNestedArrays: Contract, storageLayout: StorageLayout;
|
let testNestedArrays: Contract, storageLayout: StorageLayout;
|
||||||
const nestedStructArray: Array<Array<{[key: string]: any}>> = [];
|
const nestedStructArray: Array<Array<{[key: string]: any}>> = [];
|
||||||
|
@ -106,12 +106,10 @@ const getDecodedValue = async (getStorageAt: GetStorageAt, blockHash: string, ad
|
|||||||
const { encoding, numberOfBytes, label: typeLabel, base, value: mappingValueType, key: mappingKeyType, members } = types[type];
|
const { encoding, numberOfBytes, label: typeLabel, base, value: mappingValueType, key: mappingKeyType, members } = types[type];
|
||||||
|
|
||||||
let value: string, proof: { data: string };
|
let value: string, proof: { data: string };
|
||||||
const arrayMatch = [...typeLabel.matchAll(/\[([0-9]*)\]/g)];
|
const [isArray, arraySize] = typeLabel.match(/\[([0-9]+)\]$/) || [false];
|
||||||
|
|
||||||
// If variable is array type.
|
// If variable is array type.
|
||||||
if (arrayMatch.length && base) {
|
if (Boolean(isArray) && base) {
|
||||||
const arraySize = arrayMatch[arrayMatch.length - 1][1];
|
|
||||||
|
|
||||||
return getArrayValue(getStorageAt, blockHash, address, types, mappingKeys, slot, base, Number(arraySize));
|
return getArrayValue(getStorageAt, blockHash, address, types, mappingKeys, slot, base, Number(arraySize));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,6 +145,18 @@ const getDecodedValue = async (getStorageAt: GetStorageAt, blockHash: string, ad
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'dynamic_array': {
|
||||||
|
if (base) {
|
||||||
|
const { slot: dynamicArraySlot, size } = await getDynamicArrayInfo(getStorageAt, blockHash, address, slot, offset, numberOfBytes);
|
||||||
|
|
||||||
|
return getArrayValue(getStorageAt, blockHash, address, types, mappingKeys, dynamicArraySlot, base, size);
|
||||||
|
} else {
|
||||||
|
throw new Error('Missing base type for dynamic array.');
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`Encoding ${encoding} not implemented.`);
|
throw new Error(`Encoding ${encoding} not implemented.`);
|
||||||
}
|
}
|
||||||
@ -197,6 +207,15 @@ export const getMappingSlot = (types: Types, mappingSlot: string, keyType: strin
|
|||||||
return slot;
|
return slot;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getDynamicArrayInfo = async (getStorageAt: GetStorageAt, blockHash: string, address: string, slot: string, offset: number, numberOfBytes: string) => {
|
||||||
|
const { value } = await getInplaceValue(getStorageAt, blockHash, address, slot, offset, numberOfBytes);
|
||||||
|
const size = Number(getValueByType(value, 'uint'));
|
||||||
|
const paddedSlot = utils.hexZeroPad(slot, 32);
|
||||||
|
slot = utils.keccak256(paddedSlot);
|
||||||
|
|
||||||
|
return { size, slot };
|
||||||
|
};
|
||||||
|
|
||||||
const getArrayValue = async (getStorageAt: GetStorageAt, blockHash: string, address: string, types: Types, mappingKeys: MappingKey[], slot: string, base: string, arraySize: number) => {
|
const getArrayValue = async (getStorageAt: GetStorageAt, blockHash: string, address: string, types: Types, mappingKeys: MappingKey[], slot: string, base: string, arraySize: number) => {
|
||||||
const resultArray = [];
|
const resultArray = [];
|
||||||
const proofs = [];
|
const proofs = [];
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.7.6;
|
||||||
|
|
||||||
|
contract TestDynamicArrays {
|
||||||
|
// Dynamic sized array variable will use 1 single slot which contains number of array elements.
|
||||||
|
int[] intArray;
|
||||||
|
|
||||||
|
// Dynamic sized array always uses the next consecutive single slot.
|
||||||
|
uint128[] uintArray;
|
||||||
|
|
||||||
|
bool[] boolArray;
|
||||||
|
|
||||||
|
address[] addressArray;
|
||||||
|
|
||||||
|
bytes10[] fixedBytesArray;
|
||||||
|
|
||||||
|
enum Choices { Choice0, Choice1, Choice2, Choice3 }
|
||||||
|
|
||||||
|
Choices[] enumArray;
|
||||||
|
|
||||||
|
// Set variable intArray.
|
||||||
|
function setIntArray(int[] calldata value) external {
|
||||||
|
intArray = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set variable uintArray.
|
||||||
|
function setUintArray(uint128[] calldata value) external {
|
||||||
|
uintArray = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set variable boolArray.
|
||||||
|
function setBoolArray(bool[] calldata value) external {
|
||||||
|
boolArray = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set variable addressArray.
|
||||||
|
function setAddressArray(address[] calldata value) external {
|
||||||
|
addressArray = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set variable fixedBytesArray.
|
||||||
|
function setFixedBytesArray(bytes10[] calldata value) external {
|
||||||
|
fixedBytesArray = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set variable enumArray.
|
||||||
|
function setEnumArray(Choices[] calldata value) external {
|
||||||
|
enumArray = value;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user