Deploying contracts on Ethereum networks is difficult, at least compared to interacting with already-deployed contracts.
Arguably, it requires an understanding of the employed virtual machine, and developer scaffolding around it, that normal people can't afford in terms of the most universal commodity: time.
For this reason, some Ethereum clients have attempted to ship popular contracts, such as multisig wallets, together with the client itself. This limits such contracts to a certain client implementation, locks the contract's development cycle to that of the client, and inevitably passes judgement on what it is that is "popular".
This is unacceptable!
Below find approaches to deploying contracts using other contracts; and wholly experimental implementations of these approaches in LLL. The latter was chosen for its atavistic outlook on the EVM, and what it is that is "allowed", or "possible".
No gods! No masters! No babysitters!
The tests are in ../tests/
.
stamping-press
(and cog
, a.k.a. greeter
)
The straightforward Factory. Used in Solidity programs with the new
keyword.
A contract's runtime bytecode contains the deployment bytecode (and, by extension, runtime) of a yet another contract.
On receiving input, the "outer layer" contract can create (optionally parametrised) copies of this nested contract.
If the copies are to be non-identical, then the "outer layer" must know their interface: that is, it must contain code that is not strictly required for its own operation, but is dependent on the implementation of the nested contract.
Upside: simple to implement and understand.
Downside: for whatever kind of contract that one would
like to be deploying, they'll need to have a separate stamping-press
.
If a change needs to be made to the nested contract template, then a
new stamping-press
will have to be deployed.
Philosophical dead-end:
Recursion is the mother of recursion.
If, instead of making objects (o
), one needs to make factories that
can make objects (Fo
), then they can nest their desired factory in a
yet another "outer layer" factory (FFo
), and be done with it.
However, on the next step attempting to do the same, it becomes
apparent that the third-layer indirection FFFo
has the same interface
(F(F..)
) as the second-layer indirection FFo
(F(F.)
), only with a
bigger nested payload.
In other words, FFFo
handles its payload in the same way as FFo
-
they have the same basic function - yet, for some reason, they are not
the same contract!
We've heard you like recursion, so...
The honeybadger Factory.
Takes an account address as input and creates a copy of that account's runtime bytecode as a new contract.
Upside: extremely simple to implement and understand.
More upsides:
No runtime? Don't care.
Runtime doesn't work without the init code? Don't care.
Even if that makes it insecure? Don't care.
Runtime makes no sense without the storage it was handling? Don't care.
Downside: needs additional steps if created contract needs
customisation (such as access controls), which likely requires
an additional contract that can perform creation (by use of the
cloning-vat
) immediately followed by initialisation, in the same
externally-originating call.
By extension, this requires initialisation code to be present in the target, even if it will only be used once, and then disabled.
More downsides:
See "more upsides" above. Don't care.
Philosophically invigorating:
A cloning-vat
handles just fine being given its own address as the
target, and creating a perfect copy of itself. It can also be given
a stamping-press
as input, or any other factory for that matter.
cannery
(and complementary can-opener
)
The one-size-fits-all Factory. Something similar has been
proposed as the copyof
or shared
keyword in Solidity.
The cannery
takes deployment bytecode (as generated by a compiler),
and stores that as code of another contract, with a protective
"wrapper".
The wrapper appears at the very beginning of a can
and REVERT
s
on any direct call to it.
To create a copy of the can
's contents, a wrapper-aware can-opener
has to be used, that can take the can
's runtime, strip it of the
REVERT
guard, and then run the deployment code in a CREATE
call.
Several approaches can be taken to the customisation of a can
after opening it. (A primitive one is shown here: concatenating the
can
's contents with caller-provided data.)
Upside: compared to the cloning-vat
, can run initialisation
code, since the cannery
gets to see it before the contract has
been deployed.
Downside: every cannery
should provide a "brand-specific"
can-opener
; an "adjustable" can-opener
can be used, but only
for trivial wrappers like above (i.e. "remove first N bytes").
Philosophically re-invigorating:
A cannery
is essentially a cloning-vat
that gets to see bytecode
at its earliest stage, before it has been deployed (and its
initialisation code lost in a historic block).
A further-specialised cannery
may perform checks on submitted
contracts - that they conform to some preordained rules; e.g. that its
runtime starts with an unconditional JUMP 35
, followed by 32 bytes,
followed by a JUMPDEST
. The 32 bytes would contain a link to the
submitted contract's source code.
A complementary specialised can-opener
may then refuse to open the
can
if it has been listed in a certain external blocklist.
Alternatively, it may refuse to open the can
if it's not been listed
in an external clearlist. It may combine both approaches as necessary.
This is just one imaginary use case for such a "delayed deployment" pattern. Many more are certainly possible.
assembly-line
(a.k.a. sequencer
)
TODO: not implemented yet!
- keeps code blocks as data in storage
- receives blueprint/sequence
- creates external contracts out of code blocks, concatenating them according to the blueprint/sequence
+
may be useful for one-shot "transactional" contracts
garden
(and seed
)
TODO: not implemented yet!
- (optionally?)
STATICCALL
s into a DELEGATECALL
into an external contract that- runs its code to deploy a yet another external contract
+
pass entire storage as "call data" (2^256 2^256 bit chunks
transferred for about 1000 gas? wow, that's cheap!..)
+
may be useful for "interruptable" contracts
-
storage operations in general are expensive