Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow immutable variables to be set in the constructor #3835

Closed
8 of 10 tasks
maurelian opened this issue Apr 5, 2018 · 42 comments
Closed
8 of 10 tasks

Allow immutable variables to be set in the constructor #3835

maurelian opened this issue Apr 5, 2018 · 42 comments
Labels
language design :rage4: Any changes to the language, e.g. new features

Comments

@maurelian
Copy link
Contributor

maurelian commented Apr 5, 2018

This request extends #715, and #3356.

Motivation:

It's very common in Solidity contracts to include storage variables which are never intended to be mutated. An example of this is decimals in EIP20. (examples: consensys, Zeppelin)

The decimals value should never change, but no one declares it as constant because they want to be able to set the value in the constructor.

This is both less safe, and more expensive because the values need to be written to, and read from storage.

Proposal:

  • immutable values should be appended to the runtime bytecode.
  • The location of the values should be stored in bytecode at compile time
  • Where constant values are currently placed on the stack using PUSH_, immutable values can be accessed using CODECOPY.

This approach has additional safety beyond compiler errors, as it's not possible to modify bytecode in the ethereum protocol.

That would look something like this:

contract Example {

    uint public constant I_AM_CONSTANT; 
    uint public immutable I_AM_IMMUTABLE;
    
    function Example(uint _b) public {
        I_AM_CONSTANT = _b; // TypeError: Cannot assign to a constant variable.
        I_AM_IMMUTABLE = _b; 
    }
    
}

Implementation notes:

  • parse additional state variable modifier "immutable" and add to AST — @chriseth -> Parsing of immutable state variable. #8444
  • check that the type is a value type — @chriseth
  • determine call graph: Which functions are only used in construction context and which functions are reachable from external functions? — @Marenz implement ImmutableValidator class #8475
    • properly handle virtual function calls
    • calls from base contructors
    • calls in member-initializations.
    • check that immutable variables are only assigned to exactly once (also not zero times) in construction context (also check the point of declaration)
    • check that immutable variables are not read from in functions called from the constructor
    • it might be necessary to re-check the above conditions for all contracts derived from a contract that defines immutable state variables because of virtual functions.
  • since assignment might happen way before actual deployment, it might be a good idea to reserve a fixed spot in memory for each immutable variable, copy there upon assignment (this might also allow multi-assignment) and then either copy to each location in bytecode before returning or use codecopy routines.
  • determine positions in bytecode where immutable variables are read - this probably requires introducing a new AssemblyItem type. For the yul code generator, this could be a new builtin function with literal argument.
  • codegen for yul
  • debug output (also part of metadata)
  • public immutables
  • documentation
  • external function pointers
@chriseth
Copy link
Contributor

chriseth commented Jun 5, 2018

The immutable keyword fits because we also plan to use it for function parameters which cannot be changed inside the function (but of course the function can be called with different arguments).

@chriseth
Copy link
Contributor

chriseth commented Jun 5, 2018

Two problems that still need to be solved are:

  • what happens if an immutable value is read by (a function called by) the constructor?
  • what happens if an immutable value is written to multiple times in (a function called by) the constructor?

A solution for both might be to provide a fixed slot in memory for the variable which is then stored in code right before the code deploy step, i.e. an immutable variable behaves completely different at construction time than at runtime (which is to be expected).

@GNSPS
Copy link
Contributor

GNSPS commented Jun 5, 2018

I think that the second scenario should revert. Immutable vars should be initializable only once.

Also re-reading this made me think if we can extend the immutable definition to encompass public function parameters as well and these would not get copied into memory and would get read directly from CALLDATA.

EDIT: in the case the public function is called externally and the parameters live in the call data, of course.

@chriseth
Copy link
Contributor

chriseth commented Jun 5, 2018

@GNSPS public function arguments have to live in memory, otherwise it is not possible to call it internally. This is different for external functions. The compiler might suggest to make a function external if it is never called internally.

@chriseth
Copy link
Contributor

chriseth commented Jun 5, 2018

Oh and I don't think it should revert, it should just not compile if you assign an immutable variable twice.

@GNSPS
Copy link
Contributor

GNSPS commented Jun 5, 2018

Oh shoot! Of course! My brain stopped, "not compile" is what I meant, "revert"ing makes no sense in a compile-time mechanism. 😂

@GNSPS
Copy link
Contributor

GNSPS commented Jun 5, 2018

In my mind, this immutable parameter thing was for the different behavior of public functions when called internally and externally but I can see how this would be so much more confusing and not save that much gas. Could just be juxtaposition of external and public functions.

@MrChico
Copy link
Member

MrChico commented Nov 24, 2019

This feature would be super nice to have imo. Especially with the istanbul increase of SLOADS, I think this can result in significant optimization gains.
If the questions above are still open, here's my 2 cents:

  • Only allow assigning to the immutable value once.
  • Ideally, the semantics would be that if a function is called which reads the immutable value before the assignment, refuse to compile.

If that turns out to be too complex since the immutable could come from memory or the bytecode, then to not allow for functions which depend on the immutable variable in the constructor seems like a good enough start!

@chriseth
Copy link
Contributor

Discussion: We might want to implement this soonish, maybe with immutable for now, but we could also combine it with the constant keyword in the future.

@fulldecent
Copy link
Contributor

fulldecent commented Jan 10, 2020

I propose there is an error in the test case. Should be:

contract Example {

    uint public constant I_AM_CONSTANT; 
    uint public immutable I_AM_IMMUTABLE;
    
    function Example(uint _b) public { // WARNING: This function can be changed to "pure" mutability
        I_AM_CONSTANT = _b; // TypeError: Cannot assign to a constant variable.
        I_AM_IMMUTABLE = _b; 
    }
    
}
 

Warning added inline

@ekpyron
Copy link
Member

ekpyron commented Jan 10, 2020

Nowadays it should rather be constructor instead of function Example, shouldn't it?

@chriseth
Copy link
Contributor

Re-added "to discuss" because we need to agree about the problems related to reading the variable before its final value has been set.

@chriseth
Copy link
Contributor

Another thing to note: This should only work for value types or at most for types that are statically-sized. In that case, we can reserve a certain spot in the deployed bytecode, do not have to move code around and can keep the metadata cbor struct at the very end.

@chriseth
Copy link
Contributor

Another complication is the order of execution of constructors in the inheritance hierarchy. Because of that, I would also say that accessing a non-initialized immutable should result in a compiler error.

@fulldecent
Copy link
Contributor

Here is a blunt way to get this to work in the next version, with further improvement possible later:

  • Accessing a property defined immutable as an r-value inside that contract's constructor shall be a compile error.

If r-value in that constructor is necessary then a workaround exists, the developer can use a temporary variable in the constructor.

@chriseth chriseth modified the milestones: Sprint 1, Sprint 2 Feb 12, 2020
@chriseth chriseth added this to the Sprint 3 milestone Feb 26, 2020
@chriseth
Copy link
Contributor

If we want to allow immutable state variables for libraries, then we have to allow them to have a constructor.

@chriseth
Copy link
Contributor

This is fully implemented now.

@fulldecent
Copy link
Contributor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
language design :rage4: Any changes to the language, e.g. new features
Projects
None yet
Development

No branches or pull requests