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
- [x] Outline the "access-control-issue" and the why behind stable state
- [ ] https://github.com/AztecProtocol/aztec-packages/issues/4319
- [ ] https://github.com/AztecProtocol/aztec-packages/issues/4320
- [ ] #4179
- [ ] https://github.com/AztecProtocol/aztec-packages/issues/4325
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_publicand aread_privatefunction.The
read_publicis the same as the normalreadon public variables, but theread_privatecan be executed in private functions against any prior public state tree (after the contract was deployed).This allows for storing "constant" values like
decimals,nameandsymbolin a way that uses the same values in private and public (no need for both aprivateand apublicstate 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_headerthey are using to build their proof. This is KNOWN and expected.The state will follow something like
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_addresslike 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_addressis really the address of therecipient, we just need the to support constraining therecipientto some contract defined value very easily - which right now is done by limiting it to only be theportal_address.A benefit of allowing the contract to pass an address along with the
contentfor 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_addressis 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