Add a Contract.constructor function so low-level constructor calls can be typechecked

In low-level calls to e.g. create2, being able to safely encode that arguments to a contract would remove a lot of potential errors.

For now you have to do something like bytes.concat(type(Contract).creationCode,abi.encode(...params)).

It would be safer to have a Contract.constructor function accessor, so one could write

bytes.concat(type(Contract).creationCode,abi.encodeCall(Contract.constructor,(...params)))

or anything else that ensures the parameters match the constructor arguments. The specific syntax proposed here is similar to abi.encodeCall(Contract.functionName,(...params)).

Additional details:

  • The function would not be callable.
  • The return type of the function would be ().

Alternatively, the following would make the change even more self-contained and not add a constructor field that can be passed around:

abi.encodeConstruction(Contract,(...params))

I wanted to say that using Contract.constructor would be problematic because you can have a function called that - but looks like you can’t. It’s disallowed even for free functions, and this makes this solution viable.

The only wart would be that it would be that the first argument of abi.encodeCall() would no longer be a function or a function type but would also have to be special-cased for this. Maybe this would be better?

abi.encodeCall(type(Contract.constructor), (...))

type(Contract.constructor) works too! Although, as long as the .constructor property is used, might as well allow it as a first-class value that can be passed around functions? And just make it non-callable?

Still type(Contract.constructor) seems reasonable; I think it enables the same degrees of freedom as abi.encodeConstructor(...).

as long as the .constructor property is used, might as well allow it as a first-class value that can be passed around functions? And just make it non-callable?

The problem is that this would allow you to assign it to a variable of function type and later to try to call a function via that pointer. Once assigned to a pointer, it becomes a black box to the compiler. Pointers can come from the user or be stored in storage variables so it’s not possible to determine at compilation time whether the pointer is pointing at the constructor and disallow calling it.

It would also be harder to be sure it does not cause problems in some obscure corner case because there are many ways to use functions and pointers. It’s not an insurmountable obstacle but given that the motivation is to use it with abi.encodeCall(), I think that confining it to type() is much safer.

It also makes more sense to me in that, semantically, it’s not a function you can call, what we want to make available is just its type.

In any case, this proposal with my modifications seems reasonable. I wonder what’s @chriseth’s and @ekpyron’s opinion.

Technically, this would be abi.encodeCall(new Contract, (...params)) :-).
new Contract has a creation function type…
That works syntactically even now, we just don’t allow it in type-checking… might actually even just work by just allowing it…

1 Like

The interesting question is whether it’s clear whether to expect only the constructor arguments, or the full creation code plus the constructor arguments already, if one encodes a contract creation call (that’s an issue independently of the syntax)…

abi.encodeCall(new Contract, (...params))

Oh, that’s nice. I didn’t even realize that it had a function type. I should start using that in tests :slight_smile:

The interesting question is whether it’s clear whether to expect only the constructor arguments, or the full creation code plus the constructor arguments already, if one encodes a contract creation call (that’s an issue independently of the syntax)…

Fair point. I guess it would be reasonable to expect something that you can pass directly into create() or create2() just like currently you get something you can directly pass into call().

Maybe we should add yet another abi. function for encoding only arguments? abi.encodeArgs()?

Actually, maybe including contract code to be deployed would even be a reasonable thing to do in @adhusson’s use case? From the example looks like they’re meant to be concatenated with it anyway.

Logically it seems that one would expect to get the full call back. It’s also nicer syntax-wise. But if it ever becomes possible to overload constructors, then you’d need a way to differentiate them here.

We already have that problem for overloaded functions - you cannot pass such a function into abi.encodeCall(). We’ll have to solve it eventually and the solution should work for the constructor too.

Another (a bit weird) reason for returning the full contract code plus arguments right away is related to EOF. The current EOF spec requires to actually modify the “data section size” field in the EOF container depending on the length of the appended constructor arguments - I’m still trying to convince people to change the spec in that regard, in order to restore the previous non-EOF behavior of just being able to append arguments without any further hickups - but if we don’t get that change, abi.encodeCall(new C, ...) could handle this complication internally, if it already includes Cs creation code.

1 Like