# RACE #9 - Proxy

{% embed url="<https://ventral.digital/posts/2022/8/29/secureum-bootcamp-epoch-august-race-9>" %}
RACE #9
{% endembed %}

<figure><img src="https://3988450783-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MWVjG_njKgBtvmnKaJh%2Fuploads%2FNxQBYg1HRxzwMKhPncKj%2Fimage.png?alt=media&#x26;token=ccce185d-1707-4aa7-b25d-55a72e113f10" alt=""><figcaption><p>RACE #9 result</p></figcaption></figure>

{% hint style="info" %}
*Note: All 8 questions in this RACE are based on the following contracts. You will see them for all the 8 questions in this RACE. The question is below the shown contract.*
{% endhint %}

```solidity
pragma solidity 0.8.7;

import "@openzeppelin/contracts/access/Ownable.sol";

// Assume the Proxy contract was deployed and no further transactions were made afterwards.

contract Mastercopy is Ownable {
    int256 public counter = 0;

    function increase() public onlyOwner returns (int256) {
        return ++counter;
    }

    function decrease() public onlyOwner returns (int256) {
        return --counter;
    }

}

contract Proxy is Ownable {
    mapping(bytes4 => address) public implementations;

    constructor() {
        Mastercopy mastercopy = new Mastercopy();
        implementations[bytes4(keccak256(bytes("counter()")))] = address(mastercopy);
        implementations[Mastercopy.increase.selector] = address(mastercopy);
        implementations[Mastercopy.increase.selector] = address(mastercopy);
    }

    fallback() external payable {
        address implementation = implementations[msg.sig];

        assembly {
            // Copied without changes to the logic from OpenZeppelin's Proxy contract.
            calldatacopy(0, 0, calldatasize())
            let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            switch result
                case 0 {
                    revert(0, returndatasize())
                }
                default {
                    return(0, returndatasize())
                }
        }
    }

    function setImplementationForSelector(bytes4 signature, address implementation) external onlyOwner {
        implementations[signature] = implementation;
    }

}
```

## Question 1 :white\_check\_mark:

The function signature is the first 4 bytes of the keccak hash which

* [x] &#x20;A. Includes the function name
* [x] &#x20;B. Includes a comma separated list of parameter types
* [ ] &#x20;C. Includes a comma separated list of return value types
* [x] &#x20;D. Is generated only for public and external functions -> I wasn't sure about this one, just memorize it

**Comment:**

A function's signature is created by hashing its name and a comma separated list (no spaces) of the types of all its parameters. For example: *add(uint256,uint256)*.

The fact that the return value type isn't part of the signature is basically given away by the fact that the creation of the *counter()* function's signature doesn't mention *int256*.

<mark style="color:red;">**Since it's used for calling external and public functions of a contract, only these functions need a signature to be called by. Internal and private functions can only be directly JUMPed to within the bytecode of the contract that contains them.**</mark>

## Question 2 :white\_check\_mark:

The Proxy contract is most similar to a

* [ ] &#x20;A. UUPS Proxy
* [ ] &#x20;B. Beacon Proxy
* [x] &#x20;C. Transparent Proxy
* [ ] &#x20;D. Metamorphic Proxy

**Comment:**

A UUPS (or Universal Upgradeable Proxy Standard) would have it's upgradeability logic within the implementation which ensures there won't be function signature clashes. This is not the case here with the *setImplementationForSelector()* function being part of the Proxy.

A Beacon Proxy would ask another contract where it can find the implementation, this isn't the case here since the implementation address is managed and stored in the Proxy contract itself.

This makes it most similar to a Transparent Proxy.

A "Metamorphic" Proxy isn't really a thing. Contracts referred to being metamorphic usually achieve upgradeability not thanks to a proxy, but due to the fact that they can be re-deployed to the same address via CREATE2.

## Question 3 :white\_check\_mark:

Gas will be saved with the following changes

* [x] &#x20;A. Skipping initialization of *counter* variable -> `int256 public counter = 0` is initializing variable to its default value
* [ ] &#x20;B. Making `increase()` function external to avoid copying from calldata to memory -> Read [this](https://ethereum.stackexchange.com/questions/19380/external-vs-public-best-practices)
* [ ] &#x20;C. Packing multiple implementation addresses into the same storage slot -> Each address is 20 bytes, can't fit into a single slot
* [ ] &#x20;D. Moving the calculation of the *counter()* function's signature hash to a constant -> Constant is just a placeholder, will be copied into runtime bytecode anyway. Constants are used for readability rather than gas optimization.

**Comment:**

Avoiding initialization of state variables to zero can indeed save gas and are usually not necessary when deploying contracts to fresh addresses where all state variables will be zero-initialized by default.

If initialization in the Mastercopy contract would attempt setting a value different from 0 it wouldn't even have any effect, since it's not setting this value in the Proxy's state - this would be considered a bug.

The *increase()* function does not have any function parameters that are being copied from calldata to memory. Introducing this change would have no effect.

Addresses are too large (20 bytes) for multiple of them to be packed into a single storage slot (32 bytes).

Constants are basically placeholders in the bytecode for expressions that are filled during compile time. It would not make a difference whether the compiler fills them or whether we've already "filled" them by hand. It might however improve readability to do so.

## Question 4 - This one tricky

Calling the *increase()* function on the Proxy contract will

* [ ] &#x20;A. Will revert since the Proxy contract has no *increase()* function -> The call will trigger the fallback function which delegatecalls the implementation contract
* [x] &#x20;B. Will revert for any other caller than the one that deployed the Proxy -> <mark style="color:red;">**Why? I don't get it.**</mark>
* [x] &#x20;C. Increases the integer value in the Proxy's storage slot located at index 1 -> Delegatecall invokes function from Mastercopy and works in Proxy's context. Slot 0 contains `_owner` from Ownable.
* [ ] &#x20;D. Delegate-call to the zero-address -> Delegatecall to the Mastercopy contract

**Comment:**

When the Proxy is called with the function signature for *increase()*, Solidity will call the *fallback()* function since the Proxy contract itself does not have a function with a matching signature.

The *fallback()* function will determine that, for this signature, it has stored the mastercontract's address as an implementation and will delegate-call it.

The Mastercontract's code will be execute in the context of the Proxy contract, meaning that the state being manipulated by the Mastercontract's code is that of the Proxy.

The function-selection logic of the Mastercontract will find that it indeed has a matching function signature belonging to *increase()* and will execute it.

The *increase()* function will increment the value of the counter state variable by one, <mark style="color:red;">**who's index is at 1 because the first index is already reserved by Ownable's owner state variable**</mark>.

This means that whatever value is currently located at the Proxy contract's storage slot with index 1 will be increased by one even if there's no variable called counter in the Proxy itself.

## Question 5 :white\_check\_mark:

Calling the *decrease()* function on the Proxy contract will

* [ ] &#x20;A. Will revert because it was not correctly registered on the proxy -> Won't revert since it is going to delegatecall the zero address
* [ ] &#x20;B. Will succeed and return the value of *counter* after it was decreased -> Irrelevant
* [ ] &#x20;C. Will succeed and return the value of *counter* before it was decreased -> Irrelevant
* [x] &#x20;D. Will succeed and return nothing -> `implementations[Mastercopy.decrease.selector]` is not registered, will delegatecall to the zero address

**Comment:**

When checking for the implementation address of the decrease() function's signature, the Proxy contract won't find one since it wasn't registered in the constructor like the increase() function was.

But that doesn't mean it'll revert, it'll instead get the default state value: The zero address.

Since no check is made to prevent *calls* when no matching signature is found in the *implementations* mapping, a delegate-call will be made to the zero address, and like all calls that are made to addresses that do not have runtime bytecode, this call will succeed without returning anything.

<mark style="color:red;">**The EVM implicitly assumes that all bytecode ends with the STOP opcode, even if the STOP opcode isn't explicitly mentioned in the bytecode itself. So to the EVM an empty bytecode actually always contains one opcode: STOP - the opcode for stopping execution without errors.**</mark>

## Question 6 :white\_check\_mark:

Due to a storage clash between the Proxy and the Mastercopy contracts

* [ ] &#x20;A. The Proxy's *implementations* would be overwritten by 0 during initialization of the Mastercopy
* [ ] &#x20;B. The Proxy's *implementations* would be overwritten when the counter variable changes
* [ ] &#x20;C. The Proxy's *implementations* variable's storage slot being overwritten causes a DoS
* [x] &#x20;D. None of the above

**Comment:**

Mappings leave their assigned storage slot unused. The actual values of a mapping are stored at location's determined by hashing the mapping slot's index with the key.

<mark style="color:red;">**That means that, even though the Proxy's**</mark><mark style="color:red;">**&#x20;**</mark>*<mark style="color:red;">**implementations**</mark>*<mark style="color:red;">**&#x20;**</mark><mark style="color:red;">**and the Mastercopy's**</mark><mark style="color:red;">**&#x20;**</mark>*<mark style="color:red;">**counter**</mark>*<mark style="color:red;">**&#x20;**</mark><mark style="color:red;">**variables make use of the same slot, they actually do not interfere with each other and nothing will happen when**</mark><mark style="color:red;">**&#x20;**</mark>*<mark style="color:red;">**counter**</mark>*<mark style="color:red;">**'s value changes.**</mark>

## Question 7 :white\_check\_mark:

The Proxy contract

* [ ] A. Won't be able to receive any ether when `calldatasize` is 0 due to a missing `receive()` -> Proxy has a payable fallback function
* [x] &#x20;B. Will be the owner of the Mastercopy contract -> In Mastercopy's storage, Ownable assigns Proxy as the owner when `Mastercopy mastercopy = new Mastercopy()` happens
* [ ] &#x20;C. Has a storage clash in slot 0 which will cause issues with the current mastercopy -> Both contracts have slot 0 storing the `_owner` variable from Ownable
* [ ] &#x20;D. None of the above

**Comment:**

Thanks to its payable *fallback()* function it'll still be able to receive ether without issues.

Ownable always initializes the owner with the msg.sender. When the Proxy deploys the Mastercopy contract, the Proxy will be the msg.sender and therefore become the owner of the mastercopy contract.

Both the Proxy contract and the Mastercopy contract first inherit from Ownable ensuring that the storage slot at index 0 will be used in the same manner on both contracts preventing any issues.

## Question 8 :white\_check\_mark:

The *fallback()* function’s assembly block

* [ ] &#x20;A. Can be marked as *"memory-safe"* for gas optimizations
* [x] &#x20;B. The result of the delegate-call overwrites the the call parameters in memory
* [ ] &#x20;C. Interferes with the Slot-Hash calculation for the implementations-mapping by overwriting the scratch space
* [ ] &#x20;D. None of the above

**Comment:**

The assembly block doesn't respect Solidity's memory management and can't be considered to be *"memory-safe"*. And even if it did, this Solidity version does not have the option to mark assembly blocks as such yet, this was introduced with version 0.8.13.

The use of the *CALLDATACOPY* opcode will copy the full *CALLDATASIZE* to memory starting at offset 0. Then, after the delegate-call was finished, the use of the *RETURNDATACOPY* opcode will copy the full *RETURNDATASIZE* to memory, also starting at offset 0. This effectively means that the output will overwrite the input of the delegate-call.

The slot-hash calculation has already finished when the assembly block begins, therefore there should not be any interference by overwriting the sratch space that was used for it.
