Why create2?Doing the “impossible” with createDeviate FishBlockedUnblockFollowFollowingFeb 21The other day, I poked my head into the Ethereum reddit world and noticed some talk about some potential vulnerabilities with a new opcode.
That opcode, known as create2, provides a new mechanism to create contracts; however, instead of creating the contract at an address derived from the creator’s address and the transaction nonce, create2 creates the contract at a location derived from the contract’s init_code, an address, and some salt (an arbitrary value).
More formally, that looks like this:keccak256(0xff ++ address ++ salt ++ keccak256(init_code))[12:]Note: It is unclear from the spec if the address is the address of the sending actor, or just some arbitrary address.
I’m assuming the former (it would be redundant with salt otherwise), but I can’t tell for sure.
The stated idea behind this is to be able to predetermine the address of a contract in advance of deployment, such that a contract might not ever have to be deployed if it ultimately ends up being necessary (I believe this is referred to as “counterfactual instantiation”).
Now, this is cool and all, but this is already mostly possible with create.
I pointed this out in a few places, and promptly found myself engaged in a discussion with one of the original proposers of the method.
For clarity, u/technocrypto is one Jeff Coleman, of L4 Ventures, but I didn’t bother to find this out until much later in the conversation, after it became clear he had a bit of an axe to grind.
He proceeded to claim that committing to specific code at a specific address, before deployment, was impossible with create.
Having become interested in whether or not it were actually possible, I began to tinker around with a prototype.
As it turns out, not only is such a thing possible, it’s actually pretty simple to do.
Before we go on, I’d like to break down this prototype a little bit, by reconstructing it from first principles.
They key objective is to prove that it’s possible to commit to specific code at a specific address, without actually deploying that code.
The most minimal exampleThis is the core of the pattern.
This is basically the factory pattern we all know and love, with the caveat that it can only create one Machine before expiring.
However, thanks to how contract addresses are generated with create, we can determine before we call commit what address our Machine will have when it is deployed.
When our MachineCommitment is created, we are now assured that the Machine can only exist at the address computed from the address of MachineCommitment and a nonce of 1.
We can even do this in Solidity, if we’d like:address(keccak256(0xd6, 0x94, address(commitment), 0x01));This pattern alone, however, will not suffice.
This takes more gas to deploy than just naively creating a new Machine, since the MachineCommitment needs to know the Machine code to be able to deploy it.
We can solve this by borrowing back from the factory pattern, in essence splitting this problem into its component parts: committing to deploy some code, and committing to deploy to some address.
What we end up with is pretty close to the prototype proposed above:The prototype with some annotationsAside: I left in some debugging (namely, the event), because it turns out this particular pattern doesn’t run properly in ethfiddle, but does run properly in Remix.
Go figure!By deploying the factory, we now create the commitment to creating some particular code.
By deploying the commitment, and supplying it only the address of the factory, we have created the commitment to a particular address.
Together, this still provides the commitment to particular code at a particular address, in a reusable and portable form.
The exact claim this pattern was proposed to solve is the following:There is no way today to guarantee the deployment, under asynchrony, by non-trusted parties, of particular code to a particular address without an insanely concocted multi-transaction hack relying on keyless signatures that are forced to precommit to specific gas prices, potentially even months or years in advance depending on your use case, and if you guess wrong all state can be permanently destroyed.
The pattern above does exactly this: it commits to deploying specific code to a specific address in advance of needing the code itself.
It does not require “multi-transaction hack[s]”, “keyless signatures”, or “precommit[ments] to specific gas prices”.
Simple modifications can be made to the pattern to support non-trusted or arbitrary parties being the deployer.
Beyond the initial commitment, no assumptions are made about the (a)syncrony of interactions involving the address.
When this particular pattern was proposed as a solution, the response wasn’t to note that it was indeed possible, but instead to critique various aspects of it.
The first critique was that anyone can call the commit method and consume the commitment themselves.
This is true, commit is a public function that anyone can call.
However, this can be solved with any authentication scheme, but might actually be a desirable property in the future.
We will come back to this point later, when we actually build something with this pattern.
The second critique was that the constructor might require arguments.
However, because we are tightly coupling our commitment to the code being deployed, this is possible with some slight modifications.
The third critique was that one requires a specific factory for each contract you want to deploy in the future.
This is valid, but again, this is merely a prototype.
We can accomplish this in a number of ways, but the simplest would be to borrow from well-known proxy patterns to create a generic commitment:A more generic commitmentAs you can see, the commitment is now completely decoupled from the code being deployed.
The factory is a generic address, and the fallback function forwards all call data to the factory, allowing arbitrary methods on the factory to be called.
In this particular example, one can use the return value from getSig on a deployed Factory as the calldata in a call to the fallback function on yourCommitment.
But what if we wanted to take it further?.What if we just wanted a commitment to some arbitrary code?.Well, it turns out we can do that, too.
In fact, it’s around this point in my research that I realized I’m not the first one to have thought this pattern up (search for “Counterfactual”).
To accomplish our goal of commitment to arbitrary code, let’s put all of the pieces together:Ain’t that a sweet lil’ thangSo, now our MachineCommitment is simply a commitment to deploy some particular code, with the help of a generic Deployer contract that can deploy the code for us.
We also get assurance that the code being deployed (the init_code, as it were) is what was committed to in the first place.
We still have the two required pieces: the commitment to a specific address, and the commitment to specific code.
But we can make this even better.
We don’t need a deployer at all, really.
It shaves a few gas off to use the delegatecall method above, but isn’t really a required feature, and adds complexity.
Instead, we can make a contract that is itself pretty much is create2, by itself.
It’s pretty simple, really:If you want to try it out in a slightly cleaner environment (no offense to ethfiddle, it’s just lacking a couple features), try heading to the Remix IDE and dropping this code in there.
You can deploy your own Machine to capture the init code values, and experiment with deploying different Create2 commitments.
This could probably be slimmed down slightly (by removing the committedAddress helper, for example).
Also, if you want to use different init_code than what is supplied above, you will need to compute the codeHash on your own.
I’ve also put this final form into a gist, in case it needs updates or fixes.
By now, we’ve reduced the gap between what create can do, and what create2 offers.
In fact, the only difference between the two is the need to publish the Create2 commitment on-chain.
Now that we’ve distilled down a pattern that enables us to commit to arbitrary code at a deterministic address… Let’s put it to use.
In my next post, I’ll create… something with this pattern.
I’m not sure what.
It might be a simple state channel, it might be another lottery (lol), or maybe something more exotic like a mixer!.Not that a mixer would require this pattern, per se, but it might be interesting nonetheless.
It’s also worth noting that the “final form” of this pattern is itself not terribly useful for any of these applications.
In fact, I think we’ll find that we’ll end up using some of the earlier revisions of the pattern in our actual implementation, because we’ll actually want to commit to specific, verifiable code, and thus have no need for the more generic variant.
I’m very interested in hearing coherent feedback about what this pattern cannot do, that create2 can do.
If you can formulate the differences programmatically, or at least in a somewhat concrete way, that would be preferred.
What I’m not interested in is hearing “we need create2 for x”, without specifying what the underlying functional need for create2 actually is.
The obvious difference is that create2 allows for committing to specific code at a specific address, without an on-chain transaction — which is cool, but seems like more of an optimization than anything else.
If anyone can describe why this is more than simply an optimization, I’d love to hear that, as well.