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

Add Stylus constructors #184

Open
wants to merge 16 commits into
base: develop
Choose a base branch
from
Open

Add Stylus constructors #184

wants to merge 16 commits into from

Conversation

gligneul
Copy link

@gligneul gligneul commented Dec 13, 2024

This feature provides a standard way to deploy, activate, and initialize a stylus contract atomically. Without constructors, it isn’t possible to guarantee that the Stylus contract initialization code will be executed before other methods.

Constructor Definition

In a Stylus contract, the constructor should be defined as the following.

**sol_storage! {
    #[entrypoint]
    pub struct Counter {
        uint256 number;
    }
}

#[public]
impl Counter {
    #[constructor]
    pub fn constructor(&mut self, count: U256) {
        self.count.set(count);
    }
    // ...
}**

There must be either no constructor definition or a single constructor for a contract. Like Solidity, function overloading for constructors is not supported.

The constructor function must be annotated with the #[constructor] attribute. It can have any name, and it will always have the signature constructor() regardless of the name and number of parameters. We hardcode the signature of the method to ensure the correct method is called when deploying the contract. If there is a method called constructor in the contract but it is not annotated with the expected macro, the SDK should emit an error stating that it expects the constructor macro.

The constructor must receive the self parameter, and it can have any number of other parameters. The values for these parameters will be passed to constructor when deploying the contract. The constructor should return no value or a result value with an unit type and a vector of bytes (Result<(), Vec<u8>>). If the constructor returns an error, the deployment will revert.

The SDK will ensure the constructor is called only once. To do so, it will wrap the constructor with a function that reads and writes to a specific slot in storage. When called, the constructor wrapper will check the contents of the specific slot and it will revert if it is different from zero. Then, after executing the constructor method, the wrapper will write a value to the specific slot, ensuring the next call to the constructor will revert. The specific slot address will be hardcoded to keccak256(”stylus_constructor”).

@gligneul gligneul changed the base branch from main to develop December 13, 2024 22:20
@gligneul gligneul marked this pull request as ready for review December 17, 2024 15:19
Copy link
Contributor

@rauljordan rauljordan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR is looking great. The implementation is concise and well-thought out. Just had a few minor comments before approval

stylus-proc/src/macros/public/mod.rs Show resolved Hide resolved
stylus-proc/src/macros/public/types.rs Outdated Show resolved Hide resolved
stylus-sdk/src/abi/internal.rs Outdated Show resolved Hide resolved
stylus-sdk/src/abi/mod.rs Outdated Show resolved Hide resolved
stylus-sdk/src/abi/mod.rs Outdated Show resolved Hide resolved
@rory-ocl
Copy link
Contributor

Looks good to me, pending testing integration with the other components.

One interesting thing about doing the constructor guard check is that it is possible to call the user- defined constructor function from other contract functions. I could see this being useful to perhaps re-initialize a contract under some certain conditions, but also it may be a source of confusion if someone reviewing the code incorrectly assumes the constructor may only be called once.

One option is to inject the constructor guard call into the body of the constructor function. If we want to allow explicit re-calling of the constructor from other constructor methods, this would have to be conditional based on disabling some feature flag.

Interested in hearing others' opinions on this.

@gligneul
Copy link
Author

Looks good to me, pending testing integration with the other components.

One interesting thing about doing the constructor guard check is that it is possible to call the user- defined constructor function from other contract functions. I could see this being useful to perhaps re-initialize a contract under some certain conditions, but also it may be a source of confusion if someone reviewing the code incorrectly assumes the constructor may only be called once.

One option is to inject the constructor guard call into the body of the constructor function. If we want to allow explicit re-calling of the constructor from other constructor methods, this would have to be conditional based on disabling some feature flag.

Interested in hearing others' opinions on this.

I prefer the current WYSIWYG approach, which doesn't change the code.

@gligneul gligneul requested a review from rauljordan December 19, 2024 20:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants