# 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="https://3988450783-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MWVjG_njKgBtvmnKaJh%2Fuploads%2FwfqUvA0f4QZ2s1MOWWcP%2Fimage.png?alt=media&#x26;token=1b83fbf0-8085-40a7-8869-2fb7a0978a81" 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="https://3988450783-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MWVjG_njKgBtvmnKaJh%2Fuploads%2F1tOk8aBcEGuz1qkXLWoh%2Fimage.png?alt=media&#x26;token=0fdd9c4a-01b3-4110-a635-72c662735f99" alt=""><figcaption></figcaption></figure>

<figure><img src="https://3988450783-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MWVjG_njKgBtvmnKaJh%2Fuploads%2FyuxVZgf4aHWyNs3tUtB6%2Fimage.png?alt=media&#x26;token=099a10b2-7b4a-4b5a-9305-16241662acdb" 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="https://3988450783-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MWVjG_njKgBtvmnKaJh%2Fuploads%2FSrxbAm5kyD9SVsxvMxQl%2Fimage.png?alt=media&#x26;token=3d48e74c-be4a-4da3-af46-157137faa9de" 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="https://3988450783-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MWVjG_njKgBtvmnKaJh%2Fuploads%2Frx5pNt5UJELa5ru1FnD5%2Fimage.png?alt=media&#x26;token=3f95e4af-3a49-4708-a878-5b6bb4760d55" 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>
