mirror of
https://github.com/cerc-io/watcher-ts
synced 2025-01-23 11:39:05 +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
|
||||
* [ ] Mapping Type
|
||||
* [ ] Dynamically-sized arrays
|
||||
* [ ] Integer Type
|
||||
* [ ] Boolean Type
|
||||
* [ ] Address Type
|
||||
* [x] Integer Type
|
||||
* [x] Boolean Type
|
||||
* [x] Address Type
|
||||
* [ ] Fixed-size byte arrays
|
||||
* [ ] Enum Type
|
||||
* [x] Enum Type
|
||||
* [ ] Dynamically-sized byte array
|
||||
* [ ] Struct 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', () => {
|
||||
let testNestedArrays: Contract, storageLayout: StorageLayout;
|
||||
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];
|
||||
|
||||
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 (arrayMatch.length && base) {
|
||||
const arraySize = arrayMatch[arrayMatch.length - 1][1];
|
||||
|
||||
if (Boolean(isArray) && base) {
|
||||
return getArrayValue(getStorageAt, blockHash, address, types, mappingKeys, slot, base, Number(arraySize));
|
||||
}
|
||||
|
||||
@ -147,6 +145,18 @@ const getDecodedValue = async (getStorageAt: GetStorageAt, blockHash: string, ad
|
||||
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:
|
||||
throw new Error(`Encoding ${encoding} not implemented.`);
|
||||
}
|
||||
@ -197,6 +207,15 @@ export const getMappingSlot = (types: Types, mappingSlot: string, keyType: strin
|
||||
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 resultArray = [];
|
||||
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