You might be interested in reading about the storage layout of dynamic arrays here.
Long story short, your string "occupies" slot 0, but this slot only contains the string size. The data itself is stored at keccak256(0)
. You need to read as many slots as required to gather all of the data.
I'm just curious regarding the size returned, it does not seem to match the length of the string present in the contract you want to read from, I'll give it a look when I have more time. In the meantime you can do something like this :
const contract = "0xB9545dFae31d62571f121C63D04Cf063c2EF8803";
// Storage slot 0 contains the size of the string, not the data
const length = await web3.eth.getStorageAt(contract, 0);
// The data is stored as keccak256(slot) : here slot is 0 as the string
// we want to read is declared at slot 0 which contains its length
const dataSlot = web3.utils.toBN(
web3.utils.soliditySha3({ t: "uint256", v: 0 })
);
const data = [];
// Loop over length // 32 slots to get the whole string data
// This iterates over as many storage slots as required
for (let i = 0; i * 32 < length; i = i + 1) {
data.push(
await web3.eth.getStorageAt(contract, dataSlot.add(web3.utils.toBN(i)))
);
}
// Convert the hexadecimal results to Utf8 and print the result
console.log(data.map((item) => web3.utils.hexToUtf8(item)).join(""));
Which prints : https://faktura.mypinata.cloud/ipfs/QmV4nWcVpGM6kbWiXBdQfcwvkcXY3WxB8ZHs47Gxp3FQch