mirror of
https://github.com/cerc-io/watcher-ts
synced 2024-11-19 20:36:19 +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', () => {
|
||||
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 () => {
|
||||
({ 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 () => {
|
||||
let { value, proof: { data: proofData } } = await getStorageValue(storageLayout, getStorageAt, blockHash, testBytes.address, 'bytesTen');
|
||||
expect(value).to.equal(bytesTenValue);
|
||||
describe('fixed size byte array', () => {
|
||||
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));
|
||||
|
||||
if (isIpldGql) {
|
||||
assertProofData(blockHash, testBytes.address, JSON.parse(proofData));
|
||||
}
|
||||
before(async () => {
|
||||
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'));
|
||||
expect(value).to.equal(bytesTwentyValue);
|
||||
await Promise.all(transactions.map(transaction => transaction.wait()));
|
||||
blockHash = await getBlockHash();
|
||||
});
|
||||
|
||||
if (isIpldGql) {
|
||||
assertProofData(blockHash, testBytes.address, JSON.parse(proofData));
|
||||
}
|
||||
});
|
||||
it('get value for fixed size byte arrays packed together', async () => {
|
||||
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 () => {
|
||||
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));
|
||||
}
|
||||
|
||||
if (isIpldGql) {
|
||||
assertProofData(blockHash, testBytes.address, JSON.parse(proofData));
|
||||
}
|
||||
({ value, proof: { data: proofData } } = await getStorageValue(storageLayout, getStorageAt, blockHash, testBytes.address, 'bytesTwenty'));
|
||||
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.
|
||||
it('get value for dynamic byte array of length less than 32 bytes', async () => {
|
||||
const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testBytes.address, 'bytesArray1');
|
||||
expect(value).to.equal(bytesArray1);
|
||||
const proofData = JSON.parse(proof.data);
|
||||
expect(proofData.length).to.equal(1);
|
||||
describe('dynamic byte array', () => {
|
||||
const byteArray1 = ethers.utils.hexlify(ethers.utils.randomBytes(20));
|
||||
const byteArray2 = ethers.utils.hexlify(ethers.utils.randomBytes(100));
|
||||
|
||||
if (isIpldGql) {
|
||||
assertProofArray(blockHash, testBytes.address, proofData);
|
||||
}
|
||||
});
|
||||
const setBytesAndGetBlock = async (value: string) => {
|
||||
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 () => {
|
||||
const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testBytes.address, 'bytesArray2');
|
||||
expect(value).to.equal(bytesArray2);
|
||||
const proofData = JSON.parse(proof.data);
|
||||
it('get value for dynamic byte array of length less than 32 bytes', async () => {
|
||||
blockHash = await setBytesAndGetBlock(byteArray1);
|
||||
const { value, proof } = await getStorageValue(storageLayout, getStorageAt, blockHash, testBytes.address, 'byteArray');
|
||||
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.
|
||||
const proofDataLength = (Math.ceil(ethers.utils.hexDataLength(bytesArray2) / 32)) + 1;
|
||||
expect(proofData.length).to.equal(proofDataLength);
|
||||
if (isIpldGql) {
|
||||
assertProofArray(blockHash, testBytes.address, proofData);
|
||||
}
|
||||
});
|
||||
|
||||
if (isIpldGql) {
|
||||
assertProofArray(blockHash, testBytes.address, proofData);
|
||||
}
|
||||
it('get value for dynamic byte array of length more than 32 bytes', async () => {
|
||||
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 { value, proof } = await getStorageAt({ blockHash, contract: address, slot });
|
||||
let length = 0;
|
||||
const proofs = [JSON.parse(proof.data)];
|
||||
const slotValue = BigNumber.from(value);
|
||||
let length = 0;
|
||||
|
||||
// Get length of bytes stored.
|
||||
if (BigNumber.from(utils.hexDataSlice(value, 0, 1)).isZero()) {
|
||||
// If first byte is not set, get length directly from the zero padded byte array.
|
||||
const slotValue = BigNumber.from(value);
|
||||
// https://docs.soliditylang.org/en/v0.7.6/internals/layout_in_storage.html#bytes-and-string
|
||||
// Check if the lowest bit is set.
|
||||
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();
|
||||
} else {
|
||||
// If first byte is set the length is lesser than 32 bytes.
|
||||
// Length of the value can be computed from the last byte.
|
||||
// If the lowest bit is not set, the value is an even number.
|
||||
// Extract the last byte of the hex string and divide by 2 to get the length.
|
||||
const lastByteHex = utils.hexDataSlice(value, 31, 32);
|
||||
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).
|
||||
// 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
|
||||
bytes bytesArray1;
|
||||
bytes bytesArray2;
|
||||
bytes byteArray;
|
||||
|
||||
// Set variable bytesTen.
|
||||
function setBytesTen(bytes10 value) external {
|
||||
@ -31,13 +30,8 @@ contract TestBytes {
|
||||
bytesThirty = value;
|
||||
}
|
||||
|
||||
// Set variable bytesArray1.
|
||||
function setBytesArray1(bytes calldata value) external {
|
||||
bytesArray1 = value;
|
||||
}
|
||||
|
||||
// Set variable bytesArray2.
|
||||
function setBytesArray2(bytes calldata value) external {
|
||||
bytesArray2 = value;
|
||||
// Set variable byteArray.
|
||||
function setByteArray(bytes calldata value) external {
|
||||
byteArray = value;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user