✅Web3 Security Research Trivia
Table of contents
✅ ABI Encoding behaves differently in memory and calldata
Dynamic types are encoded as 2-part in memory and 3-part in calldata. The extra field in the calldata case is the "offset". This field is needed because data chunks are not stored consecutively in calldata. The actual content of a dynamic types are stored behind static types.
https://twitter.com/ret2basic/status/1678633214837923840
✅ ecrecover needs zero address check because of geth implementation instead of the precompile
https://twitter.com/lovethewired/status/1681333654209941505
✅ Large uint casts to int -> overflow
For uint256 in the range 2**255
to 2**256 - 1
, casting it to int256 causes overflow. The result is a negative number. This is because the max int256 is 2**255 - 1
and max uint256 is 2**256 - 1
.
✅ Solidity' return is different from Yul's return
https://twitter.com/ret2basic/status/1699768097270563177
✅ bytes is different from bytes32
When writing function selector we should always change uint
to uint256
, but be aware that don't do the same for bytes
. bytes
is dynamic length and bytes32
is fixed length, they are absolutely different.
✅ return; is equivalent to STOP opcode
Even if the function does not have a return value, you can write return;
to stop the execution of a function's logic. I tested in Remix debugger and I found that return;
is just a STOP opcode.
✅ create2 precomputing address should consider constructor's input parameters
https://twitter.com/ret2basic/status/1700449951090909405
✅ Huff __FUNC_SIG() is done during the compilation phase
I found this in a Remix debugging session: __FUNC_SIG() actually stores the function selector into contract bytecode during compilation phase. In contrast, encodeWithSignature() in Solidity does heavy computation at runtime, so Huff is a lot more efficient.
✅ msg.sender in forge script
Without using vm.startBroadcast()
, msg.sender is just address(this).
If vm.startBroadcast()
is used, msg.sender is the the EOA address corresponding to the private key you provide via command line argument.
Another case:
In this case msg.sender is just attackerAddress.
✅ SELFDESTRUCT deletes bytecode at the very end of tx
https://twitter.com/ret2basic/status/1703261157652681181
✅ bytes32 pads 0's on the right
✅ UDMV
https://twitter.com/AmadiMichaels/status/1694815830465302882
Code:
✅ vm.startPrank() can set both msg.sender and tx.origin
✅ One way to silence linter on unused variable
This seizeTokens is unused in this function, so seizeTokens;
can be used to silence linter such as Solhint.
✅ msg.sender when calling external function using this
keyword
this
keywordWhen calling an external function within the same contract internally, you would use the this
keyword, such as this.functionCall()
where functionCall()
is an external function written in the same contract. The question is, who is msg.sender when using the this
keyword?
The answer is msg.sender = address(this)
. For a test in Remix IDE:
Call callInternalViaThis()
with any EOA address, you will see that lastInternalCaller
is set to contract address not the original EOA address.
Last updated