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
Introduce immutable variables to WASM contracts #1714
Comments
Immutable variables is a programming language feature. It is in the case of solidity and it should be in our case, too. I see no reason to bake this complexity into In this case I don't see a big advantage in moving this logic to pallet-contracts: ink! can solve this problem by flagging certain fields as read only: We remove direct field access for the root storage struct and generate accessor functions (we leave out the setter for read only variables). |
I agree with Alex here. Can you move this into the ink! repo and spec it out based on ink!'s perspective instead? |
Just checked. ink! contracts already support defining constants. With regard to immutables, we can indeed just mark certain fields with As solidity documentation states, upon construction (which I understand is instantiation) of the contract, the references to immutable variables are replaced with their actual values. So I guess we do need to alter the code on-chain and re-instantiate it as we currently do not separate constructors from the main contract body as solidity does. Alternatively, we need to expose some functionality from |
I don't think we are after a performance benefit here. |
Why not? The whole point of immutables is that you don't need to make storage reads as values get "hard coded" into the binary. |
This won't work, because in pallet-contracts we store the contract code once, and then it is shared between all contract instances. If you change the contract's code by injecting new values for an immutable variable coming from next instance's constructor execution, it would break all previously deployed instances using the same code (because it would mutate the immutable variables of them). On contrary, in EVM world each contract instance uses brand new code persisted on-chain. And this comes with a huge waste of storage. |
Agree that we definitely don't want to do this in
This would introduce a footgun because you can still update "read only" fields by calling Easiest thing would be to tell people just to use a If people really don't want to modify a const VERSION: &str= match option_env!("VERSION") {
Some(v) => v,
None => "dev",
}; and do Could even do some syntactic sugar to generate such a pattern e.g. #[ink(const)]
const MY_CONST: u32 = 420; In this case you could add the consts to the metadata and pass them as args during build e.g. Anyway just thinking out loud, but seems to me that |
One of benefits of immutables over consts is the ability to assign runtime specific data like caller's address or the balance. Consts do not allow this. The workaround I have in mind is to get this data from the dry-run and modify the source locally before uploading |
After a call with @agryaznov. We've come up with the next design proposal: It was agreed that changing contract's Wasm blob is costly and does not bring many benefits. Using constant is already possible and does not require any changes. On the ink! side, we shall introduce In the end, immutable fields will behave almost the same as normal fields except that they can not be updated. |
You are suggesting access control for storage items in |
Having discussed the potential solutions with core ink! team I am closing the solution as we currently can not provide any security guarantees to implement this feature. Solutions consideredModify the contract bundle by adding globals to the Wasm moduleThis was the original design proposed in this issue and was later discarded due to complication with gas metering and potential re-instrumentation of every deployable contract. This procedure would simply sky-rocket the gas for every instantiation. This is due to fundamental differences between ink! and solidity. Upon compilation, solidity produces a "creation" binary that is then executed on-chain to instantiate contract. ink!, on the other hand, produce the final deployable binary that gets instantiated on-chain. Therefore, we can not do on-chain instantiation of any constants or immutables. Restrict the mutability with macrosThis change does not involve any modifications on the pallet-contracts. It restricts the mutability of storage fields on the language level. The problem with that is this approach does not provide any security guarantees and creates a false sense of security as the contract storage can be overwritten with Add immutable branch in the storage treeCan be a potential solution, that does not bring much of a cost benefit, but provides security guarantees for immutability as pallet-contract would restrict the overwrite of this branch with In conclusion, it is advices for developers to adopt their own approach in maintaining the immutable storage fields in their contracts. |
Rationale
Solidity has the concept of immutable variables, which value can be set once in contract’s constructor and guaranteed to stay unchanged afterwards. OpenZeppelin has recently recommended ink! to introduce this feature too, to provide contract developers with more flexibility in designing and building secure and efficient smart contracts. In this issue we’ll try to think about how we can introduce the similar concept into ink! and Pallet Contracts.
Spec of changes on ink! side
TBD
(Original discussion): ways to solve it on the pallet side
WebAssembly has indeed the similar construct - immutable global. Its initializer lives in the Globals section of the Wasm module, and uses a constant initializing expression.
The constructor of Wasm contracts in the pallet_contracts is the
deploy
exported function. This function is called once at the time of contract instantiation on-chain. Contract instantiation is done after the re-instrumentation and uploading the contract Wasm blob on-chain, and it in general can be done in a separate (from uploading) extrinsic, when deploying a new contract instance of a code already stored on-chain. This approach allows for more optimal storage usage, and saves gas and storage deposit for the caller account. Changing the Global section of the module on the step of contract instantiation would require its code altering and re-uploading on chain, implying an additional costs for contract authors, and, more importantly, breaking these variables immutability of all other contract instances which have been deployed from this code before.Another approach could be to use an imported global in the contract module (note: currently importing globals to Wasm contracts is forbidden). Pallet contracts could define the global and initialize it in the “env” module during the instantiation of the contract module to the Wasm engine. For that it would need to somehow get the initializing expression from the constructor (
deploy
exported function of a module, which is to be designed by contract developer in#[ink(constructor)]
). One problem here is that we instantiate the module first to the Wasm engine, and only after that we call the exporteddeploy
function. Another problem is that once the immutable variable is initialized, we need to store its value for the particular contract instance living at specific AccountID. And once we don't want to store a separate code blob for each contract instance, the likely solution here is to persist the immutable variables values to the contract instance's storage.The text was updated successfully, but these errors were encountered: