> For the complete documentation index, see [llms.txt](https://ret2basic.gitbook.io/ctfnote/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ret2basic.gitbook.io/ctfnote/web3-security-research/web3-security-research-trivia.md).

# Web3 Security Research Trivia

## Table of contents

* [x] ABI Encoding behaves differently in memory and calldata
* [x] ecrecover needs zero address check because of geth implementation instead of the precompile
* [x] Large uint casts to int -> overflow
* [x] Solidity's return is different from Yul's return
* [x] bytes is different from bytes32
* [x] return; is equivalent to STOP opcode
* [x] create2 precomputing address should consider constructor's input parameters
* [x] Huff \_\_FUNC\_SIG() is done during the compilation phase
* [x] msg.sender in forge script
* [x] SELFDESTRUCT deletes bytecode at the very end of tx
* [x] bytes32 pads 0's on the right
* [x] UDMV
* [x] vm.startPrank() can set both msg.sender and tx.origin
* [x] One way to silence linter on unused variable
* [x] msg.sender when calling external function using `this` keyword
* [x] selfdestruct after Pectra update -> EIP-6780

## :white\_check\_mark: 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>

## :white\_check\_mark: ecrecover needs zero address check because of geth implementation instead of the precompile

<https://twitter.com/lovethewired/status/1681333654209941505>

## :white\_check\_mark: 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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Test{
    function overflow() public pure returns (int256) {
        uint256 temp = 2**255 + 10000000;
        return int256(temp);
    }
}
```

<figure><img src="/files/blg31IMKaqheQaLzKPPD" alt=""><figcaption></figcaption></figure>

## :white\_check\_mark: Solidity' return is different from Yul's return

<https://twitter.com/ret2basic/status/1699768097270563177>

## :white\_check\_mark: 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.

## :white\_check\_mark: 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.

## :white\_check\_mark: create2 precomputing address should consider constructor's input parameters

<https://twitter.com/ret2basic/status/1700449951090909405>

## :white\_check\_mark: 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.

## :white\_check\_mark: 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:

```solidity
        uint256 attackerPrivateKey = vm.envUint("PRIVATE_KEY");
        address attackerAddress = <your_EOA_address>;
        vm.deal(attackerAddress, 1 ether);
        vm.startBroadcast(attackerPrivateKey);
```

In this case msg.sender is just attackerAddress.

## :white\_check\_mark: SELFDESTRUCT deletes bytecode at the very end of tx

<https://twitter.com/ret2basic/status/1703261157652681181>

## :white\_check\_mark: bytes32 pads 0's on the right

<figure><img src="/files/nHE8CvbwbrFxEx4ET8Yz" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/L2gf7hFuIKAwUmRQAQr6" alt=""><figcaption></figcaption></figure>

## :white\_check\_mark: UDMV

<https://twitter.com/AmadiMichaels/status/1694815830465302882>

Code:

<pre class="language-solidity"><code class="lang-solidity"><strong>// SPDX-License-Identifier: MIT
</strong>pragma solidity ^0.8.20;
<strong>
</strong>contract x60 {
	function notGood() external pure returns(bytes memory) {
	    bytes memory data;

	    assembly {
	        mstore(0x60, 0x20)
	    }

	    return data;
	}

	function shouldBeGood() external pure returns(bytes memory) {
	    bytes memory data = new bytes(0);

	    assembly {
	        mstore(0x60, 32)
	    }

	    return data;
	}
}
</code></pre>

## :white\_check\_mark: vm.startPrank() can set both msg.sender and tx.origin

<figure><img src="/files/grQgMHQD3LGj0Oc86RJx" alt=""><figcaption></figcaption></figure>

{% embed url="<https://book.getfoundry.sh/cheatcodes/start-prank>" %}

## :white\_check\_mark: One way to silence linter on unused variable

<figure><img src="/files/MwjutOP1r5rYRkTc8jCS" alt=""><figcaption></figcaption></figure>

This seizeTokens is unused in this function, so `seizeTokens;` can be used to silence linter such as Solhint.

## :white\_check\_mark: msg.sender when calling external function using `this` keyword

When 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:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SenderPreservationExample {
    address public lastDirectCaller;
    address public lastInternalCaller;

    function setCallerDirect() public {
        lastDirectCaller = msg.sender;
    }

    function setCallerInternal() public {
        lastInternalCaller = msg.sender;
    }

    function callInternalViaThis() public {
        this.setCallerInternal();
    }

    function getCallers() public view returns (address, address) {
        return (lastDirectCaller, lastInternalCaller);
    }
}
```

Call `callInternalViaThis()` with any EOA address, you will see that `lastInternalCaller` is set to contract address not the original EOA address.

### :white\_check\_mark: selfdestruct after Pectra update -> EIP-6780

[EIP-6780](https://eips.ethereum.org/EIPS/eip-6780) says:

> The new functionality (of selfdestruct opcode) will be only to send all Ether in the account to the target, except that the current behaviour is preserved when `SELFDESTRUCT` is called in the same transaction a contract was created.

This can be proven by a series of tests: <https://gist.github.com/ret2basic/9464cd936e020aa2ba1c0b6f8f2f800a>
