You are mixing storage references with memory references. When the doc states :
It returns a reference to the element
It's a storage reference, since .push()
is only valid on dynamic storage arrays (at least currently).
However, when you take that storage reference as a memory variable (which is in itself a reference to a memory location to be precise) then some special rules apply as described in the documentation :
Assignments between storage and memory (or from calldata) always
create an independent copy.
And :
Assignments from storage to a local storage variable also only assign
a reference.
So, going over the 3 cases that you have using the rules above :
Case 1 :
uint item = storageDynamicUintArray.push();
assigns a storage reference (.push()
) to a local uint
variable living on the stack. This is an independent copy by definition.
Case 2 :
A memory memoryItem = storageDynamicArrayOfA.push();
assigns a storage reference to a memory variable, this is also an independent copy as per the first rule.
Case 3 :
A storage storageItem = storageDynamicArrayOfA.push();
assigns a storage reference to a local storage variable, this is a reference as per the second rule.
The documentation is not wrong, it's just that the very specific portion you were reading did not mention the caveats of data location and assignment behavior.
I hope that answers your question.
If your data was in calldata (data location) you could simply use slices.
Unfortunately, slices are not available on memory arrays.
how would I be able to retrieve the string "hello"? There is no
abi.decodeWithSelector, or is there? For my use-case it would even be
sufficient to just shift the entire data by 8 bytes to the left, but
then shl(n,data) (assembly) doesn't work for reference types.
shl
doesn't care about reference or value types, it just takes a value and shifts it. Your issue is that you didn't gave it the right value : data
is a memory pointer, while you want to shift the actual data contained there.
Now you will need to do a bit more than just shifting if your data is more than 32 bytes long (which is always true with dynamic types such as string
due to the way abi encoding works).
To do so, you could use the following function, which works regardless of length, type or number of arguments :
function extractCalldata(bytes memory calldataWithSelector) internal pure returns (bytes memory) {
bytes memory calldataWithoutSelector;
require(calldataWithSelector.length >= 4);
assembly {
let totalLength := mload(calldataWithSelector)
let targetLength := sub(totalLength, 4)
calldataWithoutSelector := mload(0x40)
// Set the length of callDataWithoutSelector (initial length - 4)
mstore(calldataWithoutSelector, targetLength)
// Mark the memory space taken for callDataWithoutSelector as allocated
mstore(0x40, add(calldataWithoutSelector, add(0x20, targetLength)))
// Process first 32 bytes (we only take the last 28 bytes)
mstore(add(calldataWithoutSelector, 0x20), shl(0x20, mload(add(calldataWithSelector, 0x20))))
// Process all other data by chunks of 32 bytes
for { let i := 0x1C } lt(i, targetLength) { i := add(i, 0x20) } {
mstore(add(add(calldataWithoutSelector, 0x20), i), mload(add(add(calldataWithSelector, 0x20), add(i, 0x04))))
}
}
return calldataWithoutSelector;
}
And call it to extract the part of interest :
function example() public pure returns (string memory) {
string memory helloString = "hello";
bytes memory dataWithSelector = abi.encodeWithSelector(0x12345678, helloString);
return abi.decode(extractCalldata(dataWithSelector), (string));
}
Which outputs hello
as expected.
I hope this answers your question.
Best Answer
The format is
Now, I think you made a mistake while calling the function by without passing string between " ". You have to call function accessTheString, by passing a string enclosed in " " ex: accessTheString("hello");
I tested this in remix, and its working