
{"id":36000,"date":"2025-01-15T11:36:47","date_gmt":"2025-01-15T11:36:47","guid":{"rendered":"https:\/\/mycryptomania.com\/?p=36000"},"modified":"2025-01-15T11:36:47","modified_gmt":"2025-01-15T11:36:47","slug":"it-wont-byte-omni-chain-deployment-factories-and-the-create3-library","status":"publish","type":"post","link":"https:\/\/mycryptomania.com\/?p=36000","title":{"rendered":"It won\u2019t byte. Omni-Chain deployment factories and the Create3 Library"},"content":{"rendered":"<p>I was working on a new project recently and wanted to do something new. It seems that all the rage these days is omni-chain deployments, contracts deployed to the same address on different chains. Given that people constantly annoy me to update addresses on our documentation for all our different chains, it sounded like a good idea to me. There\u2019s lots of ways to do these kinds of deployments, but in my research I was particularly intrigued by one method: Deployment Factories. These are contracts that use incredibly intricate knowledge of the EVM to deploy contracts deterministically. This led me down a rabbit hole of inline-assembly that help make me a better developer, and have some fun along the\u00a0way.<\/p>\n<h3>Constructors<\/h3>\n<p>Everyone who has ever deployed a smart contract knows you need a constructor. It\u2019s the piece of code that runs only once when you deploy your smart contract. It runs only once, but has a special property. In it, you can set an <em>immutable variable. <\/em>These variables are declared alongside your other storage variables with the \u201cimmutable\u201d keyword. Once they\u2019re set, they are constant and can\u2019t be changed, just like variables with \u201cconstant\u201d. The differences and similarities however are fascinating. Constant variables are set at compile-time. The solidity compiler goes in and replaces all references to the variable with the literal variable. Immutable variables however, are set at <em>creation time. <\/em>There\u2019s two different types of EVM-code: Creation Code and Runtime\u00a0Code.<\/p>\n<h4>Code Types<\/h4>\n<p>Creation Code is the EVM bytecode that runs when your contract is deployed. It can do everything, but its purpose is actually to create the runtime time. At the end of a constructor the returned data isn\u2019t a value, but bytecode itself. The creation code\u2019s job is to construct and return the runtime code, that runs every time the contract is called. When you define a value for an immutable variable in your constructor, the creation code is actively going through all the references to it in the runtime-code, and replacing it with a literal value. This is why it\u2019s immutable. Since the value is hard-coded into the runtime bytecode, changing its value means changing the contract byte-code, which can\u2019t be done once deployed. Remember this, that the value returned from creation code is ONLY the runtime code of that newly deployed contract.<\/p>\n<p>Another interesting quirk of the constructor, is that its parameters are pushed on in strange orderings. When you call a function in solidity, the first 4-bytes of the calldata are the function-selector, followed by the abi-encoding of the parameters. A contract deployment call does the opposite, and appends the parameters to the end of the creation-code, not the beginning.<\/p>\n<p>An example deployment of a\u00a0contract<\/p>\n<p>You can see this in an example deployment transaction I did recently. The address 0xC4C319E2D4d66CcA4464C0c2B32c9Bd23ebe784e is for the alETH-ETH curve pool, and I passed it as a parameter to the contract on creation. This will matter\u00a0later.<\/p>\n<p>Example Constructor<\/p>\n<h3>Create and\u00a0Create2<\/h3>\n<p>When i was researching these deployment factories I was curious how to use them, and how they worked. Like many, my experience with creation factories is with the <a href=\"https:\/\/github.com\/OpenZeppelin\/openzeppelin-contracts\/blob\/master\/contracts\/proxy\/Clones.sol\">OpenZeppelin Clones Library<\/a>. This library let you clone an existing contract into a new address. Using the create2 opcode, you can even clone a contract to a deterministic location. However, this comes with caveats. When you clone a contract, you can\u2019t run a constructor on it. This is because of quirks in the <a href=\"https:\/\/eips.ethereum.org\/EIPS\/eip-1167\">ERC-1167 standard<\/a> used by the library, so you need to use an initializer function if you want to change something.<\/p>\n<p>cloneDeterministic Function in OpenZeppelin Clone\u00a0Libraru<\/p>\n<p>When I was looking up contracts that were deployed using this factory, I noticed that none of them actually used an initializer function, they all had constructors and set immutable variables. I wondered how this was possible since the factories used create2 to allow the user to deterministically decide where to deploy the contract to. I started to dive into the factory code itself. I ended up learning a lot about how the EVM and contract creation works as a\u00a0result.<\/p>\n<p>Solmate Create3.sol Factory\u00a0Deployer<\/p>\n<p>Above is the bread and butter of the Solmate Create3.sol deployment factory. In less than 15 lines of code, you get a master class in the EVM deployment process. Let\u2019s go through\u00a0it.<\/p>\n<h3>The function<\/h3>\n<p>The function takes in 3 values: a salt, creation code, and value. Value is simply how much eth you would like to deploy your new contract with, in Wei. Salt is an arbitrary value decided by the user when calling. Pick any 32-byte value in the world. I choose to use the keccak256 of a string related to the purpose of the contract. The contract seems daunting but it\u2019s really not so let\u2019s dive in to what\u2019s going\u00a0on<\/p>\n<p>bytes memory proxyChildBytecode = PROXY_BYTECODE;<br \/>address\u00a0proxy;<\/p>\n<p>Let\u2019s skip over this for right now, but know that we\u2019re simply taking some constant bytecode and moving it into memory so it is easier and cheaper to access later. We\u2019re also going to allocate in memory a slot for an\u00a0address;<\/p>\n<p>proxy\u00a0:= create2(0, add(proxyChildBytecode, 32), mload(proxyChildBytecode), salt)<\/p>\n<p>There\u2019s a lot going on here so let\u2019s go step by\u00a0step:<\/p>\n<p>Create2 Explanation<\/p>\n<p>The create2 opcode takes 4\u00a0values:<\/p>\n<p>The amount of ether to send to the new\u00a0contractAn location in memory to start reading\u00a0from.The size of our creation code, in\u00a0bytes.A salt to act as a seed in the address generation.<\/p>\n<p>The opcode takes in all of these, determines the address of a new contract based on it, and deploys a contract to that address using the creationCode of size <em>size<\/em> beginning at <em>offset<\/em>. Not that hard so far. What you might have noticed however, is that we\u2019re not deploying the creation code supplied by the user, but some creation code defined as constant, <em>PROXY_BYTECODE<\/em>. That\u2019s right. Instead, we\u2019re gonna deploy a very small and optimized contract instead. Stay with me\u00a0here.<\/p>\n<p>When you store a bytes object in memory in solidity, the compiler also needs to store the length of that object. The first memory slot, before the object itself stores this length. This is why we\u00a0did<\/p>\n<p>bytes memory proxyChildBytecode = PROXY_BYTECODE;add(proxyChildBytecode, 32)<\/p>\n<p>We stored our bytecode in memory, but we don\u2019t want to deploy the length of the bytecode also, so we\u2019re going to simply jump over it. We take the slot the creation code was placed into, and move one more slot, 32-bytes to the right where the actual bytecode\u00a0begins.<\/p>\n<p>PROXY_BYTECODE is the creation code of our new proxy contract being created by that create2 call. It\u2019s only 14-bytes long, but incredibly powerful. Let\u2019s go through\u00a0it<\/p>\n<p>0x67\u200a\u2014\u200aPUSH8\u00a0bytecode<\/p>\n<p>We\u2019re going to push 8-bytes onto the stack. The 8-bytes in the top section of that table. This is going to be our actual contract bytecode that we want created when the create2 is\u00a0over.<\/p>\n<p>0x3d &amp; 0x52\u200a\u2014\u200aRETURNDATASIZE &amp;\u00a0MSTORE<\/p>\n<p>We\u2019re gonna push a zero onto the stack and then call mstore. This means we\u2019re going to store the 8 bytes we pushed on first starting at memory offset 0. As I write this the PUSH0 opcode was added recently into the EVM, and as a result this factory may be redeployed.<\/p>\n<p>0x6008\u200a\u2014\u200aPUSH1 08<br \/>0x6018\u200a\u2014\u200aPUSH1\u00a018<\/p>\n<p>We push the values 8 and 24 (18 in hex = 24 decimal) onto the\u00a0stack<\/p>\n<p>0xf3\u200a\u2014\u200aRETURN<\/p>\n<p>The return opcode takes in 2 values: an offset, and a size. So we are saying to the EVM, return 8-bytes starting at offset-24. So you\u2019re probably wondering now, why offset 24? This is a quirk of the EVM, when writing to memory MSTORE only lets you write 32-bytes at a time. If you try to store a value less than 32-bytes in length, it will left-pad that value with 0\u2019s out to 32. Since our bytecode that we want returned is only 8-bytes in length it will be padded when stored in memory as <strong>\u201c00000000\u2026.bytecode\u201d,<\/strong> 24-bytes of zeros followed by our 8-byte bytecode. But we don\u2019t want to return all those zeros, just our bytecode, so we say \u201creturn me only the 8 bytes of data beginning at offset-24\u201d which is our intended bytecode.<\/p>\n<p>So our creation code has run, and it\u2019s set some random 8-bytes of bytecode. But what do we do with this and what is that bytecode? Let\u2019s keep going through the\u00a0solidity<\/p>\n<p>require(proxy\u00a0!= address(0), \u201cDEPLOYMENT_FAILED\u201d);<\/p>\n<p>This is a simple check to make sure that our create2 of our proxy works. proxy in this case is the address of our new proxy contract that we just set the runtimeCode to. But what why did we do\u00a0this?<\/p>\n<p>(bool success, ) = proxy.call{value: value}(creationCode);<\/p>\n<p>Now we get to use our parameters from earlier. We\u2019re going to make a call to this new contract we made. We\u2019re sending in value, and using creationCode as the only parameter. This creationCode is the code of the actual contract we want deployed. So how does the contract\u00a0execute?<\/p>\n<p>0x36\u200a\u2014\u200aCALLDATASIZE<\/p>\n<p>First we push the size of our creation code onto the\u00a0stack<\/p>\n<p>0x3d 0x3d\u200a\u2014\u200aRETURNDATASIZE<\/p>\n<p>Followed by two zero\u2019s. You\u2019ll see why in a\u00a0second<\/p>\n<p>0x37\u200a\u2014\u200aCALLDATACOPY<\/p>\n<p>CALLDATACOPY takes 3\u00a0inputs:<\/p>\n<p>A destination offset in memory to start writing\u00a0atthe offset in the calldata to start reading\u00a0fromThe number of bytes to\u00a0read<\/p>\n<p>So with this opcode we\u2019re telling it we want to copy the entirety of the calldata into memory, starting at offset zero. Remember that at this point the calldata is the creation code of our intended new contract.<\/p>\n<p>0x36\u200a\u2014\u200aCALLDATASIZE<br \/>0x3d\u200a\u2014\u200aRETURNDATASIZE<br \/>0x34\u200a\u2014\u200aCALLVALUE<\/p>\n<p>Push the size of our creation code again followed by another zero and then finally the amount of wei to send to the new contract.<\/p>\n<p>0xf0\u200a\u2014\u200aCREATE<\/p>\n<p>Unlike CREATE2, CREATE works slightly differently. It\u00a0takes<\/p>\n<p>An amount of wei to\u00a0sendan offset in memory to start reading\u00a0fromThe amount of data to read as creation\u00a0code.<\/p>\n<p>So now our stack reads {value, 0, size(creationCode)}, and we\u2019ve stored our creation code in memory at offset 0. So we\u2019re telling the EVM to create a new contract using that creation code. The creation code that we passed in originally as a parameter. Now, for CREATE, the address of the new contract is based on the address of the deployer and its nonce. Since we just deployed this deployer proxy-contract, the nonce is zero, and our address\u00a0is<\/p>\n<p>address = keccak256(rlp([sender_address,sender_nonce]))[12:]<\/p>\n<p>So why does this matter? Think about this from an omni-chain deployment standpoint. We used a Create Factory to deploy a proxy contract deterministically. It always deploys the same creation code. If our factory is deployed to address(0x123) on every chain, then as long as I use the same salt on every chain, the proxy contract it creates will have the same address. All of these contracts in the same transaction call create. However, since all of them have nonce 0, since we just created the contract, the address of the contract it creates will be the same as well. So you\u2019re probably wondering \u201cwhy do they need the proxy contract? Why not just use create2 every time and deploy based on the salt and creation code?\u201d. that\u2019s a good question. There actually is no reason you couldn\u2019t do that. However, it makes deployments tougher. The address returned from create2 is based on the creationCode itself.<\/p>\n<p>initialisation_code = memory[offset:offset+size]<br \/>address = keccak256(0xff + sender_address + salt + keccak256(initialisation_code))[12:]<\/p>\n<p>As long as the salt and the initialization code is the same then you could deploy easily. However, by using a proxy setup like we have here, we make it even easier. Now the address of your deployment is based ONLY on what the provided salt is. You only need to keep track of one thing. In fact, we can take this further too. Let\u2019s say you wanted to upgrade this contract. How would you do it? You could use proxy patterns like UUPS, but that\u2019s cumbersome and involves additional risk. What if you instead used selfdestruct first to delete the contract? Since the address of our new contract is based on the salt only, you can deploy an upgraded version of a contract, with different bytecode, to the same address later. If you only used Create2 and changed the creation code, the address would be different. Upgradability without proxy interface contracts.<\/p>\n<p><strong>So how do you use\u00a0this?<\/strong><\/p>\n<p>So you probably saw that code earlier and said \u201cwell how do I get my contract\u2019s creation code to pass to the deployer?\u201d I was also interested in that. Luckily the solidity maintainers have you\u00a0covered.<\/p>\n<p>Example Deployment using a\u00a0factory<\/p>\n<p>Everyone knows you can use <strong><em>address.code<\/em><\/strong> to get the bytecode of a contract, but this actually misses some of the nuance. When you use that, the compiler converts it into the\u00a0opcode<\/p>\n<p>EXTCODECOPY and EXTCODESIZE<\/p>\n<p>However, this only works for deployed contracts. If we want to get the raw bytecode of a non-deployed contract we can use type(C).creationCode or type(C).runtimeCode. This fields actually compile down to the literal bytecode of those contracts, and inserts it directly into the bytecode of your parent contract at compile-time. This is certainly cool, but what about constructor parameters? This is the question that sent me down this whole rabbit hole. How do I put together creation code that includes parameters to pass to the factory? Recall earlier how I pointed out that constructor parameters are actually stored at the end of the creation code? This is where it matters. By simply appending our parameters to our creation-code, set at compile-time, we have now put together all the code we need to\u00a0deploy.<\/p>\n<p>In the above example I tried to deploy an ERC-20 token using the parameters for name and symbol. I did this because they\u2019re set in the constructor and immutable<\/p>\n<p>OpenZeppelin ERC20.sol constructor<\/p>\n<p>I then passed that creation code, alongside a random salt I chose, and deployed it. As you can see, I then checked the contract I had deployed to see if the parameters I included had been set, and they had. The immutable variable I had wanted it to set had been done correctly.<\/p>\n<p>If I wanted to deploy this same contract with the same inputs to another chain I would simply use a foundry-cheatcode to switch the chain and run the same code again, the output would be the same. Remember this works because as long as the salt is the same, the newly deployed contract will be as\u00a0well.<\/p>\n<h3>The Lesson<\/h3>\n<p>It\u2019s very easy to see a bunch of inline assembly and get scared off. But when you really start to pick it apart, it\u2019s not that difficult. There\u2019s so many incredible hacks people have managed to come up with for the EVM and its amazing to constantly see innovation in the space. This certainly gave me a kick in the ass to keep working on my assembly skills, and I hope this does the same for y\u2019all. This article was inspired by the ZeframLou CREATE3 Factory Contract and the Solmate Create3.sol library. I highly recommend you check it out, as I used the Zefram contract in my tests while writing this\u00a0article.<\/p>\n<p><a href=\"https:\/\/github.com\/ZeframLou\/create3-factory\">GitHub &#8211; ZeframLou\/create3-factory<\/a><\/p>\n<p><a href=\"https:\/\/github.com\/0xSequence\/create3\/blob\/master\/contracts\/Create3.sol\">https:\/\/github.com\/0xSequence\/create3\/blob\/master\/contracts\/Create3.sol<\/a><\/p>\n<p><a href=\"https:\/\/medium.com\/coinmonks\/it-wont-byte-learning-not-to-fear-assembly-through-omni-chain-deployments-5ca82253c224\">It won\u2019t byte. Omni-Chain deployment factories and the Create3 Library<\/a> was originally published in <a href=\"https:\/\/medium.com\/coinmonks\">Coinmonks<\/a> on Medium, where people are continuing the conversation by highlighting and responding to this story.<\/p>","protected":false},"excerpt":{"rendered":"<p>I was working on a new project recently and wanted to do something new. It seems that all the rage these days is omni-chain deployments, contracts deployed to the same address on different chains. Given that people constantly annoy me to update addresses on our documentation for all our different chains, it sounded like a [&hellip;]<\/p>\n","protected":false},"author":0,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2],"tags":[],"class_list":["post-36000","post","type-post","status-publish","format-standard","hentry","category-interesting"],"_links":{"self":[{"href":"https:\/\/mycryptomania.com\/index.php?rest_route=\/wp\/v2\/posts\/36000"}],"collection":[{"href":"https:\/\/mycryptomania.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mycryptomania.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"replies":[{"embeddable":true,"href":"https:\/\/mycryptomania.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=36000"}],"version-history":[{"count":0,"href":"https:\/\/mycryptomania.com\/index.php?rest_route=\/wp\/v2\/posts\/36000\/revisions"}],"wp:attachment":[{"href":"https:\/\/mycryptomania.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=36000"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mycryptomania.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=36000"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mycryptomania.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=36000"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}