mirror of
https://github.com/cerc-io/watcher-ts
synced 2025-01-21 18:49:06 +00:00
Fix getting bytes value length from storage slot in solidity mapper (#313)
* Add failing test case for bytes with leading zeros * Fix getting bytes value length from storage slot
This commit is contained in:
parent
28023d834a
commit
69e9402dc1
@ -383,76 +383,148 @@ describe('Get value from storage', () => {
|
|||||||
|
|
||||||
describe('byte array', () => {
|
describe('byte array', () => {
|
||||||
let testBytes: Contract, storageLayout: StorageLayout, blockHash: string;
|
let testBytes: Contract, storageLayout: StorageLayout, blockHash: string;
|
||||||
const bytesTenValue = ethers.utils.hexlify(ethers.utils.randomBytes(10));
|
|
||||||
const bytesTwentyValue = ethers.utils.hexlify(ethers.utils.randomBytes(20));
|
|
||||||
const bytesThirtyValue = ethers.utils.hexlify(ethers.utils.randomBytes(30));
|
|
||||||
const bytesArray1 = ethers.utils.hexlify(ethers.utils.randomBytes(24));
|
|
||||||
const bytesArray2 = ethers.utils.hexlify(ethers.utils.randomBytes(100));
|
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
({ contract: testBytes, storageLayout } = contracts.TestBytes);
|
({ contract: testBytes, storageLayout } = contracts.TestBytes);
|
||||||
|
|
||||||
const transactions = await Promise.all([
|
|
||||||
testBytes.setBytesTen(bytesTenValue),
|
|
||||||
testBytes.setBytesTwenty(bytesTwentyValue),
|
|
||||||
testBytes.setBytesThirty(bytesThirtyValue),
|
|
||||||
testBytes.setBytesArray1(bytesArray1),
|
|
||||||
testBytes.setBytesArray2(bytesArray2)
|
|
||||||
]);
|
|
||||||
|
|
||||||
await Promise.all(transactions.map(transaction => transaction.wait()));
|
|
||||||
blockHash = await getBlockHash();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('get value for fixed size byte arrays packed together', async () => {
|
describe('fixed size byte array', () => {
|
||||||
let { value, proof: { data: proofData } } = await getStorageValue(storageLayout, getStorageAt, blockHash, testBytes.address, 'bytesTen');
|
const bytesTenValue = ethers.utils.hexlify(ethers.utils.randomBytes(10));
|
||||||
expect(value).to.equal(bytesTenValue);
|
const bytesTwentyValue = ethers.utils.hexlify(ethers.utils.randomBytes(20));
|
||||||
|
const bytesThirtyValue = ethers.utils.hexlify(ethers.utils.randomBytes(30));
|
||||||
|
|
||||||
if (isIpldGql) {
|
before(async () => {
|
||||||
assertProofData(blockHash, testBytes.address, JSON.parse(proofData));
|
const transactions = await Promise.all([
|
||||||
}
|
testBytes.setBytesTen(bytesTenValue),
|
||||||
|
testBytes.setBytesTwenty(bytesTwentyValue),
|
||||||
|
testBytes.setBytesThirty(bytesThirtyValue)
|
||||||
|
]);
|
||||||
|
|
||||||
({ value, proof: { data: proofData } } = await getStorageValue(storageLayout, getStorageAt, blockHash, testBytes.address, 'bytesTwenty'));
|
await Promise.all(transactions.map(transaction => transaction.wait()));
|
||||||
expect(value).to.equal(bytesTwentyValue);
|
blockHash = await getBlockHash();
|
||||||
|
});
|
||||||
|
|
||||||
if (isIpldGql) {
|
it('get value for fixed size byte arrays packed together', async () => {
|
||||||
assertProofData(blockHash, testBytes.address, JSON.parse(proofData));
|
let { value, proof: { data: proofData } } = await getStorageValue(storageLayout, getStorageAt, blockHash, testBytes.address, 'bytesTen');
|
||||||
}
|
expect(value).to.equal(bytesTenValue);
|
||||||
});
|
|
||||||
|
|
||||||
it('get value for fixed size byte arrays using single slot', async () => {
|
if (isIpldGql) {
|
||||||
const { value, proof: { data: proofData } } = await getStorageValue(storageLayout, getStorageAt, blockHash, testBytes.address, 'bytesThirty');
|
assertProofData(blockHash, testBytes.address, JSON.parse(proofData));
|
||||||
expect(value).to.equal(bytesThirtyValue);
|
}
|
||||||
|
|
||||||
if (isIpldGql) {
|
({ value, proof: { data: proofData } } = await getStorageValue(storageLayout, getStorageAt, blockHash, testBytes.address, 'bytesTwenty'));
|
||||||
assertProofData(blockHash, testBytes.address, JSON.parse(proofData));
|
expect(value).to.equal(bytesTwentyValue);
|
||||||
}
|
|
||||||
|
if (isIpldGql) {
|
||||||
|
assertProofData(blockHash, testBytes.address, JSON.parse(proofData));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('get value for fixed size byte arrays using single slot', async () => {
|
||||||
|
const { value, proof: { data: proofData } } = await getStorageValue(storageLayout, getStorageAt, blockHash, testBytes.address, 'bytesThirty');
|
||||||
|
expect(value).to.equal(bytesThirtyValue);
|
||||||
|
|
||||||
|
if (isIpldGql) {
|
||||||
|
assertProofData(blockHash, testBytes.address, JSON.parse(proofData));
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Dynamically sized byte array.
|
// Dynamically sized byte array.
|
||||||
it('get value for dynamic byte array of length less than 32 bytes', async () => {
|
describe('dynamic byte array', () => {
|
||||||
const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testBytes.address, 'bytesArray1');
|
const byteArray1 = ethers.utils.hexlify(ethers.utils.randomBytes(20));
|
||||||
expect(value).to.equal(bytesArray1);
|
const byteArray2 = ethers.utils.hexlify(ethers.utils.randomBytes(100));
|
||||||
const proofData = JSON.parse(proof.data);
|
|
||||||
expect(proofData.length).to.equal(1);
|
|
||||||
|
|
||||||
if (isIpldGql) {
|
const setBytesAndGetBlock = async (value: string) => {
|
||||||
assertProofArray(blockHash, testBytes.address, proofData);
|
const transaction = await testBytes.setByteArray(value);
|
||||||
}
|
await transaction.wait();
|
||||||
});
|
return getBlockHash();
|
||||||
|
};
|
||||||
|
|
||||||
it('get value for dynamic byte array of length more than 32 bytes', async () => {
|
it('get value for dynamic byte array of length less than 32 bytes', async () => {
|
||||||
const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testBytes.address, 'bytesArray2');
|
blockHash = await setBytesAndGetBlock(byteArray1);
|
||||||
expect(value).to.equal(bytesArray2);
|
const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testBytes.address, 'byteArray');
|
||||||
const proofData = JSON.parse(proof.data);
|
expect(value).to.equal(byteArray1);
|
||||||
|
const proofData = JSON.parse(proof.data);
|
||||||
|
expect(proofData.length).to.equal(1);
|
||||||
|
|
||||||
// Length is equal to slots required by the data plus the initial slot used to calculate the actual slots holding the data.
|
if (isIpldGql) {
|
||||||
const proofDataLength = (Math.ceil(ethers.utils.hexDataLength(bytesArray2) / 32)) + 1;
|
assertProofArray(blockHash, testBytes.address, proofData);
|
||||||
expect(proofData.length).to.equal(proofDataLength);
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (isIpldGql) {
|
it('get value for dynamic byte array of length more than 32 bytes', async () => {
|
||||||
assertProofArray(blockHash, testBytes.address, proofData);
|
blockHash = await setBytesAndGetBlock(byteArray2);
|
||||||
}
|
const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testBytes.address, 'byteArray');
|
||||||
|
expect(value).to.equal(byteArray2);
|
||||||
|
const proofData = JSON.parse(proof.data);
|
||||||
|
|
||||||
|
// Length is equal to slots required by the data plus the initial slot used to calculate the actual slots holding the data.
|
||||||
|
const proofDataLength = (Math.ceil(ethers.utils.hexDataLength(byteArray2) / 32)) + 1;
|
||||||
|
expect(proofData.length).to.equal(proofDataLength);
|
||||||
|
|
||||||
|
if (isIpldGql) {
|
||||||
|
assertProofArray(blockHash, testBytes.address, proofData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('get value for dynamic byte array with leading zeros and of length less than 32', async () => {
|
||||||
|
const byteArray = ethers.utils.hexZeroPad(byteArray1, 24);
|
||||||
|
blockHash = await setBytesAndGetBlock(byteArray);
|
||||||
|
const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testBytes.address, 'byteArray');
|
||||||
|
expect(value).to.equal(byteArray);
|
||||||
|
const proofData = JSON.parse(proof.data);
|
||||||
|
expect(proofData.length).to.equal(1);
|
||||||
|
|
||||||
|
if (isIpldGql) {
|
||||||
|
assertProofArray(blockHash, testBytes.address, proofData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('get value for dynamic byte array with leading zeros and of length more than 32', async () => {
|
||||||
|
const byteArray = ethers.utils.hexZeroPad(byteArray2, 110);
|
||||||
|
blockHash = await setBytesAndGetBlock(byteArray);
|
||||||
|
const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testBytes.address, 'byteArray');
|
||||||
|
expect(value).to.equal(byteArray);
|
||||||
|
const proofData = JSON.parse(proof.data);
|
||||||
|
|
||||||
|
// Length is equal to slots required by the data plus the initial slot used to calculate the actual slots holding the data.
|
||||||
|
const proofDataLength = (Math.ceil(ethers.utils.hexDataLength(byteArray) / 32)) + 1;
|
||||||
|
expect(proofData.length).to.equal(proofDataLength);
|
||||||
|
|
||||||
|
if (isIpldGql) {
|
||||||
|
assertProofArray(blockHash, testBytes.address, proofData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('get value for dynamic byte array of length 31', async () => {
|
||||||
|
const byteArray = ethers.utils.hexlify(ethers.utils.randomBytes(31));
|
||||||
|
blockHash = await setBytesAndGetBlock(byteArray);
|
||||||
|
const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testBytes.address, 'byteArray');
|
||||||
|
expect(value).to.equal(byteArray);
|
||||||
|
const proofData = JSON.parse(proof.data);
|
||||||
|
expect(proofData.length).to.equal(1);
|
||||||
|
|
||||||
|
if (isIpldGql) {
|
||||||
|
assertProofArray(blockHash, testBytes.address, proofData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('get value for dynamic byte array of length 32', async () => {
|
||||||
|
const byteArray = ethers.utils.hexlify(ethers.utils.randomBytes(32));
|
||||||
|
blockHash = await setBytesAndGetBlock(byteArray);
|
||||||
|
const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testBytes.address, 'byteArray');
|
||||||
|
expect(value).to.equal(byteArray);
|
||||||
|
const proofData = JSON.parse(proof.data);
|
||||||
|
|
||||||
|
// Length is equal to slots required by the data plus the initial slot used to calculate the actual slots holding the data.
|
||||||
|
const proofDataLength = (Math.ceil(ethers.utils.hexDataLength(byteArray) / 32)) + 1;
|
||||||
|
expect(proofData.length).to.equal(proofDataLength);
|
||||||
|
|
||||||
|
if (isIpldGql) {
|
||||||
|
assertProofArray(blockHash, testBytes.address, proofData);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -351,17 +351,20 @@ const getInplaceValue = async (getStorageAt: GetStorageAt, blockHash: string, ad
|
|||||||
*/
|
*/
|
||||||
const getBytesValue = async (getStorageAt: GetStorageAt, blockHash: string, address: string, slot: string) => {
|
const getBytesValue = async (getStorageAt: GetStorageAt, blockHash: string, address: string, slot: string) => {
|
||||||
const { value, proof } = await getStorageAt({ blockHash, contract: address, slot });
|
const { value, proof } = await getStorageAt({ blockHash, contract: address, slot });
|
||||||
let length = 0;
|
|
||||||
const proofs = [JSON.parse(proof.data)];
|
const proofs = [JSON.parse(proof.data)];
|
||||||
|
const slotValue = BigNumber.from(value);
|
||||||
|
let length = 0;
|
||||||
|
|
||||||
// Get length of bytes stored.
|
// Get length of bytes stored.
|
||||||
if (BigNumber.from(utils.hexDataSlice(value, 0, 1)).isZero()) {
|
// https://docs.soliditylang.org/en/v0.7.6/internals/layout_in_storage.html#bytes-and-string
|
||||||
// If first byte is not set, get length directly from the zero padded byte array.
|
// Check if the lowest bit is set.
|
||||||
const slotValue = BigNumber.from(value);
|
if ((slotValue.and(1)).toNumber() !== 0) {
|
||||||
|
// If the lowest bit is set, the value is an odd number.
|
||||||
|
// So subtract 1 and divide by 2 to get the length.
|
||||||
length = slotValue.sub(1).div(2).toNumber();
|
length = slotValue.sub(1).div(2).toNumber();
|
||||||
} else {
|
} else {
|
||||||
// If first byte is set the length is lesser than 32 bytes.
|
// If the lowest bit is not set, the value is an even number.
|
||||||
// Length of the value can be computed from the last byte.
|
// Extract the last byte of the hex string and divide by 2 to get the length.
|
||||||
const lastByteHex = utils.hexDataSlice(value, 31, 32);
|
const lastByteHex = utils.hexDataSlice(value, 31, 32);
|
||||||
length = BigNumber.from(lastByteHex).div(2).toNumber();
|
length = BigNumber.from(lastByteHex).div(2).toNumber();
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,7 @@ contract TestBytes {
|
|||||||
// If data is 32 or more bytes, the main slot stores the value length * 2 + 1 and the data is stored in keccak256(slot).
|
// If data is 32 or more bytes, the main slot stores the value length * 2 + 1 and the data is stored in keccak256(slot).
|
||||||
// Else the main slot stores the data and value length * 2.
|
// Else the main slot stores the data and value length * 2.
|
||||||
// https://docs.soliditylang.org/en/v0.7.4/internals/layout_in_storage.html#bytes-and-string
|
// https://docs.soliditylang.org/en/v0.7.4/internals/layout_in_storage.html#bytes-and-string
|
||||||
bytes bytesArray1;
|
bytes byteArray;
|
||||||
bytes bytesArray2;
|
|
||||||
|
|
||||||
// Set variable bytesTen.
|
// Set variable bytesTen.
|
||||||
function setBytesTen(bytes10 value) external {
|
function setBytesTen(bytes10 value) external {
|
||||||
@ -31,13 +30,8 @@ contract TestBytes {
|
|||||||
bytesThirty = value;
|
bytesThirty = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set variable bytesArray1.
|
// Set variable byteArray.
|
||||||
function setBytesArray1(bytes calldata value) external {
|
function setByteArray(bytes calldata value) external {
|
||||||
bytesArray1 = value;
|
byteArray = value;
|
||||||
}
|
|
||||||
|
|
||||||
// Set variable bytesArray2.
|
|
||||||
function setBytesArray2(bytes calldata value) external {
|
|
||||||
bytesArray2 = value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user