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

Feat: "Stable" state variable #4130

Closed
5 tasks done
LHerskind opened this issue Jan 18, 2024 · 3 comments
Closed
5 tasks done

Feat: "Stable" state variable #4130

LHerskind opened this issue Jan 18, 2024 · 3 comments

Comments

@LHerskind
Copy link
Contributor

LHerskind commented Jan 18, 2024

The What

The "stable" state variable is a "quick-n-dirty" application enforced immutable - it is way to have something that can be read in both private and public execution.

The state variable is stored in public state, but support both a read_public and a read_private function.
The read_public is the same as the normal read on public variables, but the read_private can be executed in private functions against any prior public state tree (after the contract was deployed).

This allows for storing "constant" values like decimals, name and symbol in a way that uses the same values in private and public (no need for both a private and a public state variable).

The values MUST be constant and set in the constructor! If they are updated later on, private execution can "choose" which value to use by altering what historical_header they are using to build their proof. This is KNOWN and expected.

The state will follow something like

struct StableState<T, T_SERIALIZED_LEN>{
    context: Context,
    storage_slot: Field,
    serialization_methods: TypeSerializationInterface<T, T_SERIALIZED_LEN>,
}

impl<T, T_SERIALIZED_LEN> StableState<T, T_SERIALIZED_LEN> {
    pub fn new(
        context: Context,
        storage_slot: Field,
        serialization_methods: TypeSerializationInterface<T, T_SERIALIZED_LEN>,
    ) -> Self;
    
    // Should initialize the value ONLY if it is not already set
    pub fn initialize(self, value: T);

    // Should read the value in public, same as `read` on public state
    pub fn read_public(self) -> T;

    // Should read in private, 
    // performs public_state inclusion proofs in historical_header
    pub fn read_private(self) -> T;
}

The Why

The reason that stable values is interested, is really because it is a "worse" version of immutables with public/private access.

So why are immutables interested?

Say you have a private donation contract, that whenever people are sending funds, it will split the funds 50/50 between charity A and charity B, essentially like 0xsplits. Since we don't want the user to define A and B but have them defined in contract we need some kind of storage that can be accessed by anyone in private.

The simples way we have with private storage variables are putting it into an immutable singleton and then having everyone use that - Simple!

However, we have an issue here, we need everyone to know the pre-image of the note that we put into the singleton, otherwise they cannot prove knowledge of it 🤔.

So how do we share this public secret? We could emit it as an unencrypted log, but now anyone using it need to have parsed through the logs and we need some standard way to share this.
Personally I dislike this solution with a passion, it adds a dependency of logs to execution.

But say we do that for now.

Our donation is popular, but organizations and daos would like to have support for public donations so they can easily show that they donated, and more easily integrate it into their contracts. For public execution we still need the split, so we put them in public state A and B and call it a day - easy.

We now have 2 version of both A and B, one immutable singleton for each of them and another public state variable for each of them. The probability that we make a fault is now much larger, as we have multiple values that should be the same, but could be made to differ (any 🪃's?).

Cost wise, we have emitted 64 bytes per public value, and 64 bytes + unencrypted logs per immutable, so we need 128bytes+ 🤮 and mess up the separation of concerns.

So say we introduce immutables, a value that is broadcast as part of the code when deploying, and can be read in both public and private, because it is just returning path of the code and not reading storage. In this case, we can have the same A and B values in both domains, making it easier to use, but at the same time reducing the costs as we don't need to emit all the extra data around what contract the data belongs to, so we can get away with just 32 bytes for each of the addresses 😎 4x+ decrease in amount of data to broadcast.

Since this require changes to deployments and need syntax and new oracles to read the data nicely the undertaking might take some time.

Shorter term, we can do the "stable" value proposed in above, throw the data in public state ~64 bytes per field in data costs, and then you simply prove in private that the value is at the expected location in public.

Portal Addresses, and how they relate

You might see how this related to portal addresses? A portal is essentially just the single immutable value that a contract have available today under an enshrined name + the caveat that outgoing messages will be sent to that address.

The reason that we ended up having the portal_address like this is relatively simple, it is the exact same issue as above, where we want the contract to know where to direct some message of funds that is consistent between the two domains, but also ease to "know".

Essentially, we don't need that the portal_address is really the address of the recipient, we just need the to support constraining the recipient to some contract defined value very easily - which right now is done by limiting it to only be the portal_address.

A benefit of allowing the contract to pass an address along with the content for cross-chain messages is that we can now send from any L2 to any L1 and then simply have the L1 contract perform access control on what it will receive messages from - it is already doing so anyway as many L2 contracts could point at the same portal.

Another place where the portal_address is used is when the L2 contract is validating the sender of the message it received. But this is done at the application circuit, and can already be altered to be whatever the contract decides, it as mentioned earlier, simply need something to constrain it against.


Tasks

Tasks

  1. LHerskind
  2. aztec.js good first issue
    sklppy88
  3. 2 of 3
    sklppy88
  4. LHerskind
@LHerskind LHerskind self-assigned this Jan 18, 2024
@LHerskind
Copy link
Contributor Author

Until we have access to the header from the context it is quite impractical to get a hold of the header value.

When #3937 is fixed it should make it fairly easy.

@LHerskind LHerskind added this to the Execution Environment milestone Jan 22, 2024
@benesjan benesjan removed the blocked label Jan 30, 2024
@benesjan
Copy link
Contributor

#3937 is tackled so this is not blocked now.

@LHerskind
Copy link
Contributor Author

#3937 is tackled so this is not blocked now.

Great. Essentially it needed it for #4179 to optimize the constraint count.

@LHerskind LHerskind removed their assignment Jan 31, 2024
LHerskind added a commit that referenced this issue Jan 31, 2024
Initial crude implementation for #4130.
The way I am getting a hold of the public values through the oracle in here is an abomination. #4320 is created to fix this.
@sklppy88 sklppy88 closed this as completed Feb 5, 2024
michaelelliot pushed a commit to Swoir/noir_rs that referenced this issue Feb 28, 2024
Initial crude implementation for AztecProtocol#4130.
The way I am getting a hold of the public values through the oracle in here is an abomination. AztecProtocol#4320 is created to fix this.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Archived in project
Development

No branches or pull requests

3 participants