Optimizing for off-chain whenever possible

A byproduct of the factory that deploys the Forwarders is that we are now able to compute the addresses of the Forwarders before we deploy them to the blockchain. This enables the same user experience as preemptively deploying a forwarding contract for each merchant, without incurring the expense of deploying these contracts on the blockchain.

In Ethereum, the addresses of the created contracts are deterministic — they can be computed from the sender’s address (i.e. the account that created the contract, in our case the factory) and the sender’s nonce (a counter tracking how many transactions originated from that account). However this does not work well in practice because the nonce is an ever increasing global counter. If a customer pays to the 100th generated address, we’d still need to deploy the initial 99 contracts so we can deploy the expected, 100th Forwarder, and we’d need to do that in a determined order. Gaps like these, when we’d need to deploy contracts just to get to the expected nonce, are inevitable because customers might create a charge but never pay a charge.

This is a problem that has plagued the Ethereum community for a long time and has been on the wishlist for many dapp developers. Fortunately, the introduction of the create2 opcode in the Constantinople network upgrade addressed this issue. Unlike create, which uses the ever-increasing sender’s nonce, create2 uses an argument-specified salt. This enables a number of workflows that were impossible or impractical before — we can now simulate complex workflows completely off-chain; without needing to track any state beyond the self-generated salt. We can safely interact with the blockchain only when we need to do an on-chain settlement. Here is how we can use the create2 opcode to deploy a Forwarder.

create2 gives control over the addresses of the deployed Forwarders. The callers can now calculate the address of the Forwarder a priori, without knowing anything about the current blockchain state.

Note that now initForwarder takes additional salt where before its functionality was performed by the implicit nonce. This allows us to explicitly decide which contracts to deploy on the blockchain, so if the 100th customer pays but the initial 99 do not, we can deploy the 100th forwarder in the sequence without needing to deploy the initial 99 first.

It’s informative to see the code that calculates the addresses:

Including the contract’s bytecode in the create2 address calculation is a crucial safety feature. It ensures that the intended contract is the only one that can ever be deployed at the calculated address.

In addition to the use of a salt, a crucial safety feature in the address generation algorithm is the use of the bytecode. Having the bytecode as one of the arguments ensures that it is impossible to deploy the “wrong” contract at the indicated address. For our example, this implies that there is exactly one combination of destination and salt that can lead to a contract being deployed at a computed address. It is impossible for a malicious attacker to deploy a contract of their own logic or their destination to an address we already computed. Therefore, we can present these non-deployed addresses to the customers and be certain that an attacker could not out-race us by deploying a contract of their own choosing to that particular address.

This enables novel workflows like the one we use at Commerce. It is now possible to create and simulate immutable workflows fully off-chain, and only do the “settlement” stage on the blockchain, after the customer has already paid for their invoice.