-
Notifications
You must be signed in to change notification settings - Fork 449
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 fallback message and constructor handlers #739
Comments
Yes this would be totally doable. We would need to add a new version of
|
This is even better than what I have had in mind! If ink! settles on using the new |
Reentrancy should be denied by default. |
A question is also how we would handle the ABI. For a frictionless UX it should be possible to use either
But then Also we should then detect if the selectors of |
This is a good idea and should ideally be implemented somewhere in the I have not yet made extensive thoughts about the metadata problems but you are right in that we ideally provide a merged concept of contract metadata. Maybe this could also be implemented in the Canvas UI so that it is possible to append messages and constructor metadata to some live contract in order to simplify calling it through UI. Not sure what is going to be the best solution. We have to make sure that metadata is kept separate though so that updating the |
I want to add some clarification for point 1(Forwarder). This function takes the code of contract A and executes it in the context of the forwarder contract. It means that we apply modifications to the storage of the forwarder but using the logic from contract A. We need only implement delegate call on pallet level, default function and we will be able to have upgradable contracts. Also, we need to define rules for an upgradable contract like it has been done here. These rules are based on the realization of storage data. |
Towards a future with upgradeable ink! smart contracts.
Motivation & Related Work
In order to create an upgradeable contract on Ethereum contract authors usually have the following architecture of 2 or 3 different contracts interacting with each other:
A
which it stores on its own contract storage. The contract at addressA
normally is the logic layer of the set of contracts where all the interesting things are happening. It is also the part that is usually upgraded. Upgrading in this scheme relinks the addressA
from theForwarder
contract to a new version of the logic layer contract. If there is aData
contract theForwarder
contract also needs to store its address so that it can notify theData
layer about an upgrade for theLogic
layer. This is required so that theData
layer can protect accesses from outside.Forwarder
contract and the logic layer necessarily needs to protect itself from accesses besides that one. Optionally a contract author can decide to put their data into yet anotherdata
contract. If this is the case theLogic
contract needs to invoke messages of theData
contract in order to interact with any persisting storage data and it will have absolutely no storage to itself. When upgrading a contract only theLogic
layer is going to be swapped out while theForwarder
andData
layers are kept. TheForwarder
simply needs to relink to the newLogic
layer contract address though and the newLogic
contract needs to keep the original links to the oldData
contract so that all storage is kept as is.Logic
layer contract and must protect itself from accesses other than theLogic
layer contract or theForwarder
contract. Upgrading a contract should never swap out theData
layer contract since this would be an extraordinarily costly operation to do. Keeping and persisting the state between upgrades is critical.Upgradeability in ink!
The above scheme is also applicable to ink! smart contract. The described
Logic
andData
layers work pretty much the same as described above. However, for theForwarder
layer ink! is missing a feature to allow for wildcard message or constructor selectors. Meaning that ink! always requires the contract author to define all valid messages and constructors up-front and upon contract execution all other messages and constructors (with non-matching selectors) will be declined. Therefore with the current state of ink! theForwarder
contract is required to mirror the API exposed by theLogic
layer contract. Also this solution does not allow for changing the API of theLogic
layer, e.g. adding new messages or constructors to it or adding optional arguments to existing messages or constructors because this would necessitate adjustments in theForwarder
contract. However, theForwarder
layer is immutable entirely. So adjusting theForwader
layer in this architecture is impossible. Therefore changing the API of theLogic
layer contract is impossible.However, with respect to contract upgradeability adjusting the
Logic
layer API is going to be very important in many cases. Therefore ink! needs to provide a way to allow for this scenario. This is best done by allowing ink! contracts to accept wildcard selectors. ink! did not support this feature so far since the exact same feature can be problematic as it has been designed in Ethereum.Design
Syntactically we will model the new behavior as always through attribute proc. macros.
Since we already have an
#[ink(selector = N)]
attribute proc. macro working with ink! messages and constructors it is the natural candidate to allow a contract to accept wildcard selectors. This could be done with a syntax like#[ink(selector = _)]
. There can only ever be exactly one#[ink(selector = _)]
ink! message and constructor respectively defined for the same contract. This being an opt-in feature will guard us against problems that can happen in Ethereum smart contracts and equips this feature with the specific purpose for being used inForwarder
smart contracts.The signature of an ink! message with the
#[ink(selector = _)]
attribute will be as follows:However, the above signature is missing out on 2 things:
Logic
layer?Logic
layer back to the caller of theForward
layer?Logic
andData
layer contracts?Solution for problem 1
For problem number 1. we could add another parameter to the signature:
Note that
input
needs to be of typeVec<u8>
since it is not known at this point what the arguments are encoded in, nor how much bytes are to be expected. The problem with this solution is that it requires heap allocation as soon as there is a single byte in the input which can cause some unnecessary Wasm binary bloat and maybe even some inefficiencies that could be avoided. So far the ink! implementation tries its best to avoid heap allocations where possible.Another solution is to not introduce the
input
argument to the signature and instead provide a low-level API to forward the input to some address, e.g.ink_env::forward_input_to_address(address: Address) -> !
. The problem with this API is that it must be called before any interaction with SEAL is happening. Also it ideally never returns which brings me to the 2. point from above.Solution for problem 2
If the
Forwarder
contract successfully calls theLogic
layer then how do we handle the return value from theLogic
layer? It somehow must be propagated back to the caller of theForwarder
contract. Ideally the return value would not be required to walk back through theForwarder
contract since that would just cause unnecessary overhead. Instead SEAL would ideally provide to call a contract in tail position so that SEAL would relink the caller to the caller of the tail call caller with the same effects that a tail call in a real computation would have. @athei is this possible?Solution to problem 3
The wildcard annotated ink! message will only ever be called if the other defined messages with their particular selectors do not match the incoming selector. This kind of shadows some portions of wildcard selectors but should not be a major problem all in all. This way it is possible to define messages such as
fn upgrade_to(logic: Address)
in order to relink bothForwarder
layer andData
layer contracts in order to use the newLogic
layer contract.The text was updated successfully, but these errors were encountered: