What does this code mean?
sender := shr(96, calldataload(sub(calldatasize(), 20)))
Btw sender is an address variable.
Ethereum Stack Exchange is a question and answer site for users of Ethereum, the decentralized application platform and smart contract enabled blockchain. It only takes a minute to sign up.
Sign up to join this communityshr(a, b)
shifts b
right by a
bits, so in this case it shifts the result of calldataload
right by 96 bits. This code essentially takes the last 20 bytes from the calldata (a.k.a. msg.data
) and removes the first 12 bytes (96 bits) from it to get a 20 byte address.
You can find all Yul (inline assembly) functions and what they do here: https://docs.soliditylang.org/en/v0.8.9/yul.html#evm-dialect
The shr
function is used to extract variables from the calldata. The calldata is the msg.data
you're passing to the contract with the function.
Imagine your calldata is this:
0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2eaabb2ee89221455302d9ffb5325689ff8f0c37b000000000000000009369ed80b93d665
When you do shr(96, calldataload(0x00))
what you're doing is adding 12 bytes to the left of the calldata (pushing the calldata to the right).
The 96 from shr(96)
is in bits, in bytes that's 96 / 8 = 12 bytes. Meaning you're adding 24 zeroes in front of the calldata (1 byte is 2 letters). So now calldata would look like this:
0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2eaabb2ee89221455302d9ffb5325689ff8f0c37b000000000000000009369ed80b93d665
Why is that important? Because you're storing the data into a variable called sender := shr(96, ...)
.
Variables in assembly are all 32 bytes (64 characters).
Therefore the variable cuts off everything after 32 bytes (or 64 characters remember that 1 bytes is 2 letters). So the variable sender becomes:
sender := 000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2
Storing successfully the first address into sender.
So that's how you extract the first parameter of the calldata.
That's it!
I figured this all by playing around with assembly in remix and seeing how the zeroes are being added.
Here's more info. Imagine your calldata being this:
0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2eaabb2ee89221455302d9ffb5325689ff8f0c37b000000000000000009369ed80b93d665
Basically what it contains is all this data combined into one:
0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2
eaabb2ee89221455302d9ffb5325689ff8f0c37b
000000000000000009369ed80b93d665
Here's the breakdown:
First parameter is an address
the length is 42 characters, 2 characters are 1 byte. Addresses in solidity are 40 characters long (0x14 hex). This will be important later on.
Second address
(notice how the 0x at the start is removed because it's only purpose is to tell the compiler that the data you're sending is hexadecimal)
Third parameter is the number 663892645828089500
expressed in hexadecimal padded with zeroes so it has a length of 16 bytes which means it's a uint128.
All variables in assembly are 32 bytes (0x20 in hex) or 64 characters. Therefore addresses are represented with 24 zeroes in front of them, because addresses are 40 characters long, they need those extra 24 to have a length of 64 characters and become 32 bytes, which then becomes a valid address variable in assembly.
Numbers in assembly are expressed in hexadecimal because it allows you to store more data in less space. A uint256 is 32 bytes long or 64 characters. A uint128 is 16 bytes or 32 characters long. Once you convert the number to hex, you add zeroes to the left to make it a valid variable.
Hope that was helpful.