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).