I have a desired storage slot:
bytes32 public slot = keccak256(abi.encodePacked("mydesiredStorageslot"));
I wish to store on this slot an url which, if not shorted, can be more than 32 bytes as well.
Assume the url is www.google.com/something-long-which-needs-to-be-stored/blah-blah-blah/blah-blah-blah/1.json
I need to write a code which essentially can store this string at this slot and also retrieve it if called.
I have written the following code which does not work for string which are more than 32 bytes. (My knowledge on assembly is not that great!). What is the best way to go about coding this?
contract Foo {
bytes32 public constant location = keccak256("mystoragelocation");
bytes32 public constant location2 = keccak256(abi.encodePacked(location));
function storeString(string memory _string) public {
bytes32 _location = location;
bytes32 _location2 = location2;
assembly {
sstore(_location, mload(_string)) // length
sstore(_location2, mload(add(_string, 0x20))) // value
}
}
function boo() public view returns (string memory p) {
bytes32 _location = location;
bytes32 _location2 = positiontwo;
assembly {
p := mload(0x40)
mstore(p, sload(_location))
mstore(add(p, 0x20), sload(_location2))
// set the pointer to free memory
mstore(0x40, add(p, 0x40))
}
}
}
Best Answer
Well first you must be aware of the string storage layout :
If the string is less that 32 bytes long, it is all written in the
length
storage slot with byte 0 encoding the string length * 2 and all other 31 bytes used to store the string data.If the string is more than 32 bytes long : the
length
storage slot holds only the value of string length * 2 + 1, and storage slot(s) starting atkeccak256(length_slot)
hold the data.So you must be able to handle both cases, and translate that to assembly which could look more or less like this for both read and write :
The code is commented, but don't hesitate to ask if some parts are a bit confusing.
EDIT : I modified the code so that the assembly doesn't do a return from the tx, allowing you or anyone to call it from another function, but stays compatible with direct invocation from the tx.
I hope that answers your question.
EDIT : Answering @Kunal Jadhav's comments.
Sure, in that case we need to read a short array (i.e., up to 31 bytes of data and 1 byte of length packed together in a single slot) and then write it according to the memory layout of strings where length and data are separated.
So let's take your example :
As this is a short string, you see that byte 0 is length * 2 (0x0a is 10, or 5 * 2) all other bytes are the string data.
Just takes the last byte of the storage slot with
and(stringLength, 0xFF)
and divides it by 2 to get the true length of the string as this is how it should be written in memory.The next line just stores that value as the first element of the
string memory
that we will return :Next we need to extract the data, the 31 higher bytes in that case. This is done with :
Breaking it down,
add(returnBuffer, 0x20)
just skips the length field that we wrote previously to start writing data where it will be expected to be for a memory string. The data extraction is done withand(stringLength, not(0xFF))
which is equivalent toand(stringLength, 0xFFFF...FFFF00)
. This simply selects the upper 31 bytes of the slot, and discards the last one that is storing the length, this value is stored to `returnBuffer + 0x20, completing the string memory layout.Once again, the memory layour is : 32 bytes for length, n * 32 bytes for data. Case
0x00
is for short array, so for a storage short array we will need to write both data and length * 2 in the same slot.Where
mload(add(_string, 0x20)
selects the data of the string in memory which can only be up to 31 bytes long since we are in case0x00
,mul(stringLength, 2)
simply computeslength * 2
, following the storage layout of short strings.or(mload(add(_string, 0x20)), mul(stringLength, 2))
combines those 2 values into a single 32 byte values where byte 0 is length * 2 , and all others are the string data.The wrapping
sstore
writes this value in thelength
storage slot. I agree that it can be confusing to write data in thelength
field, but this is how it's done for short string arrays.As per your comment :
ADD(mload(add(_string, 0x20)), mul(stringLength, 2)))
would work too, I just prefer to use binary operators when working with bits, and arithmetic operators when working with numbers. But in that case both are equivalent.