Do values get cloned?

Hi there, just wondering if values in the template contract get cloned as well using the Clones library. Say in the template contract, a mapping(uint256 => string) has a key-value pair <10, “hello”>, does the mapping in the cloned contract also have this value as well?

Would that cause any issue in checking if a certain value exists in the new contract? Thanks.

Yes, I think it does. Which is why it's so expensive when cloning a large Mapping.

Hey man, I did an experiment given below. The answer is actually no. The cloned contract does not have the <10, “hello”> value.

This makes me wonder if a mapping object is part of the bytecode of a contract? What is exactly happening in a clone, since it is called a clone? Does it copy a mapping as a mapping functionality or as a mapping storage? I guess it is the former. Correct?

// SPDX-License-Identifier: MIT

pragma solidity 0.8.4;

import "@openzeppelin/contracts/proxy/Clones.sol";

import "hardhat/console.sol";

contract Template {

    mapping(uint256 => string) aMap;

    function set(uint256 key, string memory val) public {

        aMap[key] = val;

    }

    function get(uint256 key) public view returns(string memory) {

        return aMap[key];

    }

}

interface ITemplate {

    function set(uint256, string memory) external;

    function get(uint256) external returns(string memory);

}

contract CloneTemplate {

    address templateAddr;

    address newTemplateAddr;

    event Set(uint256 key, string val);

    event Get(uint256 key, string val);

    

    function setTemplateAddr(address addr) public {

        templateAddr = addr;

    }

    function createTemplate() public {

        newTemplateAddr = Clones.clone(templateAddr);

    }

    function set(uint256 key, string memory val) public {

        ITemplate(newTemplateAddr).set(key, val);

        emit Set(key, val);

    }

    function get(uint256 key) public returns(string memory) {

        string memory output;

        output = ITemplate(newTemplateAddr).get(key);

        console.log(output);

        emit Get(key, output);

        return output;

    }

}
1 Like

As I was trying to get a deeper understanding of mapping, I experimented with three contracts: 1. a blank empty contract; 2. a contract with a mapping(uint256=>string) aMap; 3. a contract with a mapping(uint256=>string) private aMap. All three contracts return bytecodes of the same length, yet with one chunk of bytecodes that are different among three. I am wondering what is a mapping represented in a contract. Is it stored as a mapping functionality or a mapping storage + functionality? But clearly, the later is not true. OK, here is the bytecode of the three cases:

'0x6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfea2646970667358221220_dbc3bee527d1a4b2626a4d6320b05b3f3e347a339939910f4d0517a8f697d567_64736f6c63430008040033'

'0x6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfea2646970667358221220_56834bd4763755e13ff70d3305f8b933b29b4b4cf275d6181f410a56f5b21c60_64736f6c63430008040033'

'0x6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfea2646970667358221220_9606eee4287c6de9b2951f90e06ab16679fae460ca53f03c55240f89719a5aeb_64736f6c63430008040033'

Could anyone please tell me how to read the bytecode to see what is happening here?

1 Like

Perhaps it’s with the function. With my cloning I do this, but perhaps their functions do it differently? I’m also kind of confused as to why I would clone, but not keep the values.

Hey @cameel , please also take a look at this problem in this post.

I also did a follow up study on the creationCode and runtimeCode. Please see the following.

The part that is puzzling me is a contract with a internal or a private mapping has the same creationTime code with an empty contract. A contract with a public mapping has a bunch more. Could you help explain why this is the case? Thanks.

// SPDX-License-Identifier: MIT

pragma solidity 0.8.4;

contract Test1 {
    mapping(uint256 => address) _aMap;
}

contract Test2 {
}

contract Test3 {
    mapping(uint256 => address) public _aMap;
}

contract Test4 {
    // 0x6080604052_348015600f57600080fd5b50603f80601d6000396000f3fe6080604052_600080fdfea26469706673582212202496b1be668ea300c5a3aed098fc1113f06166cba9ca165f0169bd1125ca464664736f6c63430008040033
    function getCreationCode1() public pure returns(bytes memory) {
        return type(Test1).creationCode;
    }

    // 0x6080604052_600080fdfea26469706673582212202496b1be668ea300c5a3aed098fc1113f06166cba9ca165f0169bd1125ca464664736f6c63430008040033
    function getRuntimeCode1() public pure returns(bytes memory) {
        return type(Test1).runtimeCode;
    }

    // 0x6080604052_348015600f57600080fd5b50603f80601d6000396000f3fe6080604052_600080fdfea26469706673582212209892b4c158e7b79da1d47a381695136070588cbe6eced2d68fbeee7964a4375564736f6c63430008040033
    function getCreationCode2() public pure returns(bytes memory) {
        return type(Test2).creationCode;
    }

    // 0x6080604052_600080fdfea26469706673582212209892b4c158e7b79da1d47a381695136070588cbe6eced2d68fbeee7964a4375564736f6c63430008040033
    function getRuntimeCode2() public pure returns(bytes memory) {
        return type(Test2).runtimeCode;
    }

    //0x6080604052_34801561001057600080fd5b506_10184806100206000396000f3fe608060405234801561001057_600080fd5b506004361061002b5760003560e01c8063c955e85f14610030575b600080fd5b61004a600480360381019061004591906100a8565b610060565b60405161005791906100e0565b60405180910390f35b60006020528060005260406000206000915054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000813590506100a281610137565b92915050565b6000602082840312156100ba57600080fd5b60006100c884828501610093565b91505092915050565b6100da816100fb565b82525050565b60006020820190506100f560008301846100d1565b92915050565b60006101068261010d565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b6101408161012d565b811461014b57600080fd5b5056fea2646970667358221220e9d796ef7fa6b5a661f7c292996512ec04aaad73753479856b3baadcb4df436b64736f6c63430008040033
    function getCreationCode3() public pure returns(bytes memory) {
        return type(Test3).creationCode;
    }

    // 0x6080604052_34801561001057600080fd5b506_004361061002b5760003560e01c8063c955e85f14610030575b_600080fd5b61004a600480360381019061004591906100a8565b610060565b60405161005791906100e0565b60405180910390f35b60006020528060005260406000206000915054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000813590506100a281610137565b92915050565b6000602082840312156100ba57600080fd5b60006100c884828501610093565b91505092915050565b6100da816100fb565b82525050565b60006020820190506100f560008301846100d1565b92915050565b60006101068261010d565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b6101408161012d565b811461014b57600080fd5b5056fea2646970667358221220e9d796ef7fa6b5a661f7c292996512ec04aaad73753479856b3baadcb4df436b64736f6c63430008040033
    function getRuntimeCode3() public pure returns(bytes memory) {
        return type(Test3).runtimeCode;
    }
}

I think no. And I think for EIP1167 Proxy, what we say it clones a contract, actually, it only clones the runtime code of the contract, I think just like deploying a new implementation contract, so it will not have the original value.

1 Like

Yes, I got this part. Please take a look at the test I did. Even in creation code, there is no difference between an empty contract and a contract with a private mapping. Wondering why.

To make the motivation more clear in order to avoid any misunderstanding, since create2 uses contract creationCode to generate a new address, and an upgrade pattern exists by first selfdestructing a contract and second deploying a new one with the same contract creationCode, to the same old address, I have to understand what could and could not change creationCode, in order to use this pattern.

As @Skyge has pointed out already, Clones implements EIP-1167. It's a library providing a bunch of helper functions for deploying contracts that consist of the bytecode listed in the EIP (with the address of the template contract pasted into it).

The code from the EIP looks like this in the assembly form:

|           0x00000000      36             calldatasize          cds
|           0x00000001      3d             returndatasize        0 cds
|           0x00000002      3d             returndatasize        0 0 cds
|           0x00000003      37             calldatacopy          
|           0x00000004      3d             returndatasize        0
|           0x00000005      3d             returndatasize        0 0 
|           0x00000006      3d             returndatasize        0 0 0
|           0x00000007      36             calldatasize          cds 0 0 0
|           0x00000008      3d             returndatasize        0 cds 0 0 0
|           0x00000009      73bebebebebe.  push20 0xbebebebe     0xbebe 0 cds 0 0 0
|           0x0000001e      5a             gas                   gas 0xbebe 0 cds 0 0 0
|           0x0000001f      f4             delegatecall          suc 0
|           0x00000020      3d             returndatasize        rds suc 0
|           0x00000021      82             dup3                  0 rds suc 0
|           0x00000022      80             dup1                  0 0 rds suc 0
|           0x00000023      3e             returndatacopy        suc 0
|           0x00000024      90             swap1                 0 suc
|           0x00000025      3d             returndatasize        rds 0 suc
|           0x00000026      91             swap2                 suc 0 rds
|           0x00000027      602b           push1 0x2b            0x2b suc 0 rds
|       ,=< 0x00000029      57             jumpi                 0 rds
|       |   0x0000002a      fd             revert
|       `-> 0x0000002b      5b             jumpdest              0 rds
\           0x0000002c      f3             return

The name "Clones" is a bit misleading. The result is effectively a clone of the template contract in the sense that it has the same exact ABI (same external/public functions and exposes the same public state variables) but this is not achieved by deploying a new copy of the template. Instead you get a proxy contract that delegates all the calls to the original and passes back the return values and errors.

It does not share any storage variable values with the original. DELEGATECALL lets the proxy invoke code from the template on the storage owned by the proxy. Code called like this does not have access to any variables of the template (including mappings; they are not special in any way in that regard). It operates on variables located at the same positions but in the storage space owned by the proxy.

A big caveat for this is that the constructor of the template contract is not re-executed for the clone. Any initialization the constructor would perform is not reflected in the storage owned by the proxy. Also, if the template has any immutables whose values are computed during construction in such a way that they can be different between runs (e.g. their values come from external calls performed by the constructor) these immutables will have the exact same values as in the template - they do not get recalculated. That's because immutable values are inserted into the contract bytecode by the constructor. If you're delegating calls to the template, you're using the immutable values it has been deployed with.

Now some answers to the specific questions from the thread:

This makes me wonder if a mapping object is part of the bytecode of a contract?

No, mapping is just a data structure occupying some storage, like an array or a struct. An empty mapping takes up one slot, which is used only to "reserve" a base address for it (we use it as an ID guaranteed to be different than for any other mapping). Keys are not stored at all and values are stored at scattered locations all around the storage space. The locations are not random but they're based on the hash of the base address and the key, which makes it extremely unlikely that they overlap with each other or anything else in storage.

You can read more about how mappings and other variables are laid out in storage here: Layout of State Variables in Storage > Mappings and Dynamic Arrays.

The part that is puzzling me is a contract with a internal or a private mapping has the same creationTime code with an empty contract. A contract with a public mapping has a bunch more. Could you help explain why this is the case?

This is because the compiler has to generate extra code for public state variables to make them accessible to other contracts. You cannot really read storage of a contract from another contract so instead you get an external getter function for every such variable. There's no extra code needed for private or internal variables because these only differ in whether you get an error trying to access the variable from an inheriting contract or not and that can be checked at compilation time.

See Contracts > Getter functions.

Even in creation code, there is no difference between an empty contract and a contract with a private mapping. Wondering why.

The creation code is basically the constructor (you don't have one) and state variable initialization (you do not initialize the mapping). You also do not do any other things that would require extra initialization code (like immutables).

I have to understand what could and could not change creationCode, in order to use this pattern.

Just a tip: if you're trying to understand what the compiler does under the hood, it might be helpful to look at the assembly it generates rather than the raw bytecode. See Analysing the Compiler Output. You might also try the --ir/--ir-optimized output which will give you Yul code (i.e. the intermediate language you use in the assembly blocks), which is more readable but keep in mind that it's not necessarily equivalent to the --asm output (it has a separate code generator).

6 Likes

Thanks @cameel for all these. Insightful and precise.

Just to add the background of these discussions here so that a more complete picture can be put together.

My first impression about Create2 came from this blog post that has a fairly in-depth discussion on this topic and mentioned an alternative upgradeability pattern:

As a side note, by this point you may have noticed that having a reproducible deployment address allows you to deploy a new contract where an old one was self-destructed. You can deploy a contract, self-destruct it, and then use the same nonce to deploy again to the same address. This opens the door for alternative upgradeabilty patterns, which are explored in depth here.

Since this pattern would take the creationCode of a contract as an input, my first reaction is this could be used to upgrade any implementation contract directly.

And with the experiments on creationCode shown above, it is clear that creationCode can be easily changed. Therefore, this cannot be used to change any logic contract directly.

Given this, it is also clear that if Create2 were to be used in an alternative upgrade pattern, it must be somehow used with other contracts together. Otherwise, it makes no sense to selfdestruct a contract and deploy a contract to the same address with exactly the same creationCode, which depends on not only what is in a constructor, but also what is not in a constructor (as indicated by the public mapping example above, and all other discussions on the mechanisms of how a constructor works).

And the way of having Create2 work with other contracts to create an alternative upgrade pattern is beyond this thread and would be another topic if there is any issue.

@maxareo Yeah, this upgrade pattern works and the article you linked (The Promise and the Peril of Metamorphic Contracts) says exactly how to do that with the use of a helper contract that invokes CREATE2 and also supplies the runtime code to the external call from your constructor. It reminds me of another article from about the same time period with a proof of concept of this idea implemented for a hackathon: Wisps: The Magical World of create2 (see code at: https://github.com/ricmoo/will-o-the-wisp).

Theoretically, I think it should even be possible to do this without relying on another contract to supply the runtime code - you could have the contract to be upgraded receive the runtime code via a constructor parameter rather than from an external call. Then you could use oz create2 to execute CREATE2 and deploy it. Well, oz create2 is probably just a wrapper over an already deployed contract so technically you still have two contracts but at least you do not have to deploy one yourself.

Note that oz CLI is no longer maintained, and I believe this feature required the user to have deployed a factory beforehand anyway.

Do you know if there is a predeployed "universal" factory contract that people could use? This sort of singleton contract tends to be challenging for developer experience because it requires a reliable way to get an instance in every network.

1 Like

Yeah, maybe my talk is misleading, too. I said I think just like deploying a new implementation contract, so it will not have the original value. what I want to say is it will not share any storage variable values with the original. In a way, just like deploying a new implementation. Maybe this is a misnomer, anyway it just uses delegatecall. Maybe you can read EIP-1167 again, and you will gain some new understanding.

1 Like

Note that oz CLI is no longer maintained, and I believe this feature required the user to have deployed a factory beforehand anyway.

Oh, right. Just noticed that the page says that the SDK is no longer developed. Thanks for the heads up.

Do you know if there is a predeployed “universal” factory contract that people could use? This sort of singleton contract tends to be challenging for developer experience because it requires a reliable way to get an instance in every network.

Not really. I saw @maxareo is interested in experimentation so I was mostly just throwing ideas. This being challenging is probably the reason why these experiments popped up shortly after CREATE2 was introduced and then kinda died off. It's very likely impractical though but still interesting to think about.

2 Likes