-
Notifications
You must be signed in to change notification settings - Fork 143
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
NEP-480: Account Namespaces #480
Changes from all commits
ce04936
a313091
c192f43
4b3854c
0bcafe3
5d7bbc3
52c9906
5ae93a7
70a2950
b479eaf
1cb0c23
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,143 @@ | ||||||||||||||
--- | ||||||||||||||
NEP: 480 | ||||||||||||||
Title: Account namespaces | ||||||||||||||
Authors: Jacob Lindahl <@encody>, Firat Sertgoz <@firatNEAR> | ||||||||||||||
Status: Draft | ||||||||||||||
DiscussionsTo: https://github.com/near/NEPs/pull/480 | ||||||||||||||
Type: Protocol | ||||||||||||||
Version: 1.0.0 | ||||||||||||||
Created: 2023-05-12 | ||||||||||||||
LastUpdated: 2023-06-20 | ||||||||||||||
--- | ||||||||||||||
|
||||||||||||||
## Summary | ||||||||||||||
|
||||||||||||||
This proposal introduces account namespaces, which enable deploying multiple WASM binaries to a single account. A WASM binary is deployed to a unique, deployer-selected namespace. Subsequent deployments to the same namespace overwrite the code currently deployed to that namespace. The empty namespace is considered the default namespace. | ||||||||||||||
|
||||||||||||||
A WASM binary deployed to a non-default (non-empty) namespace can be invoked explicitly by specifying the namespace in the action. If no namespace or the empty string is specified, then the default namespace is used. | ||||||||||||||
|
||||||||||||||
State is isolated between namespaces. That is to say, a contract deployed to `alice.near`'s default namespace cannot read or modify the storage of a contract deployed to `alice.near`'s `multisig` namespace. As described by this NEP, interactions between different namespaces can only be facilitated via reflexive function calls. | ||||||||||||||
|
||||||||||||||
## Motivation | ||||||||||||||
|
||||||||||||||
- Composing contracts is non-trivial. Currently, it is possible to deploy a single smart contract to a NEAR account using a `DEPLOY_CONTRACT` action. Subsequent `DEPLOY_CONTRACT` actions completely replace existing code with new code, while leaving all storage intact. | ||||||||||||||
|
||||||||||||||
- It is impossible for a non-developer user to compose multiple contract standards together into a single contract without actually performing some development work. Account namespaces would enable a user workflow wherein a user only needs to choose which contracts to deploy, and not worry about how to compose them. | ||||||||||||||
|
||||||||||||||
- If a developer wants their smart contract to be upgradable, they have to manually write the upgrade functionality into their smart contract, and smart contract upgrades are complex, multi-step operations with many security considerations and opportunities to "brick" the account. Namespaces would allow a developer to deploy a semi-standardized upgrade module to a namespace, and have that manage the upgrade process for the other (default, etc.) namespaces. | ||||||||||||||
|
||||||||||||||
As this is a part of the [Account Extensions upgrade](https://github.com/near/NEPs/issues/478), some of the benefits of account namespaces are realized in conjunction with other proposals from the upgrade: | ||||||||||||||
|
||||||||||||||
- Certain contracts are frequently deployed on different accounts. "Codeless contracts" allow an account to deploy just the hash of a smart contract instead of the full binary. This, in conjunction with namespaces, would allow a user to pick and choose a set of account features to deploy with negligible gas and storage costs. | ||||||||||||||
nagisa marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
|
||||||||||||||
- Permissions: It could be possible to set permissions on a particular namespace. For example, restricting the namespace `dao_multisig` to interactions with `dao.near` only. | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are these the only capabilities that you propose to enable? What about arbitrary host functions, etc.? Having read through the proposal in more detail, I think it is important to have fine grained capabilities for namespaces. What you are proposing is to allow smart contract developers who are not proficient to deploy multiple wasm modules on their account. These wasm modules could be buggy or malicious. If this proposal is expected, I can imagine some users just having a single account with all the different contracts they want to try out / experiment with and without fine grained control over their capabilities, they could end up losing a lot of funds. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure how all of the questions in this comment fit together. Currently, I don't think that the typical non-tech-savvy user deploys any code to their accounts, but please correct me if I am wrong. Specifically when speaking of non-tech-savvy users, the workflow we conceptualized is something like:
|
||||||||||||||
|
||||||||||||||
- Synchronous execution. Namespaced contracts on the same account should be able to communicate with each other synchronously instead of asynchronously. | ||||||||||||||
|
||||||||||||||
## Specification | ||||||||||||||
|
||||||||||||||
### The `Namespace` type | ||||||||||||||
|
||||||||||||||
We introduce a new primitive type: `Namespace`. A `Namespace` is a string that contains only lowercase letters, numbers, and underscores. The empty string is considered the default namespace. | ||||||||||||||
|
||||||||||||||
### `DeployContract` action | ||||||||||||||
|
||||||||||||||
The `DeployContract` action is modified to include a `namespace` field. If the `namespace` field is not specified in an RPC call, the default namespace (the empty string) is used. The attached code is then deployed to the specified namespace. | ||||||||||||||
|
||||||||||||||
If code has already been deployed to the specified namespace, it is replaced, leaving code deployed to other namespaces untouched. | ||||||||||||||
|
||||||||||||||
Once a namespace is created, this NEP does not provide a way to delete it. However, namespaces should not be considered permanent. | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason why we’d need a concept of namespace creation? Would this creation step introduce semantics meaningfully different from the empty namespace existing for accounts that haven't deployed a contract yet? If not, I think it would be quite a bit more straightforward to work with this feature if all namespaces were presumed to “exist” with a code_hash equal to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Namespace creation is approximately equal to contract deployment in this NEP. If all namespaces are presumed to exist, how does an RPC account view call show namespaces? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, I'm not sure! I imagine it would list the non- But counter-question to your question: what's the expected behaviour of calling a non-existent namespace? If my memory serves me right, in the namespace-less world of today, the behaviour is meaningfully different between calling a function in a non-existent account and calling a function in an existing account without a contract deployed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that case would be similar to calling a non-existent method on an account that does have a contract deployed. Possibly a case for a new, "namespace does not exist" error? |
||||||||||||||
|
||||||||||||||
### `FunctionCall` action | ||||||||||||||
|
||||||||||||||
The `FunctionCall` action is modified to include a `namespace` field. If the `namespace` field is not specified in an RPC call, the default namespace (the empty string) is used. The function call is then executed on the code deployed to the specified namespace. | ||||||||||||||
|
||||||||||||||
### `FunctionCallPermission` | ||||||||||||||
|
||||||||||||||
A `namespaces` field is added to `FunctionCallPermission`. This field contains a list of namespaces that the access key is allowed to call. If the `namespaces` field is not specified, the access key is allowed to call all namespaces. | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason I’m not seeing why this deviates from the usual “no namespace ⇒ empty namespace” defaulting elsewhere in this NEP? I feel like it might be a notable implicit foot gun in the use of protocol. Consider for example:
Forgetting I’ve done 2, I have accidentally allowed the important contract to be called arbitrarily by the old key(s). The alternative is to equate “empty There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right now, a function call access key that does not specify a list of methods is allowed to call any method. Therefore, a function call access key that does not specify a list of namespaces is allowed to call any namespace. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right. I feel like the stakes are likely somewhat different here in that deploying a new contract to an account in absence of namespaces overwrites the old one, and thus the user is conscious about both the previous and the new contract (and thus possibly is thinking of the access controls in context of both.) In the world of namespaces they no longer need to care about any previous state anymore. But fair, I see how the proposed behaviour is consistent. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we need a new syntax for function call access keys, wherein they can be permitted to call "any method on |
||||||||||||||
|
||||||||||||||
### NEAR VM host functions | ||||||||||||||
|
||||||||||||||
The following host functions are added to the NEAR host environment: | ||||||||||||||
|
||||||||||||||
- `predecessor_namespace() -> Namespace`: Returns the namespace of the predecessor account, which may be the empty namespace. In the case that the predecessor is not a smart contract, this is the empty namespace. | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm... so we are using the empty namespace to represent two different values: empty namespace in the predecessor or no smart contract. Could this be a problem? Is there a good way to distinguish between the two? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we usually don't care too much whether a call comes from an EOA or contract, but if the distinction is pertinent, one may use |
||||||||||||||
- `current_namespace() -> Namespace`: Returns the namespace of the current account, which may be the empty namespace. | ||||||||||||||
- `namespace_storage_usage(&Namespace) -> u64`: Returns the storage usage of the given namespace. This includes storage consumed by the contract and state, but not account data (e.g. balance, access keys). That is to say, | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What would be the value of obtaining the storage usage of a different namespace than the one currently executing? If there isn’t one, might it make sense to require that the Might be worthwhile to double check if an ability to do so does not preclude whatever other improvements we might want to make. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One of the suggested use-cases for namespaces is to support "hot-swapping" wherein a manager namespace deploys code to other namespaces and manages their state and execution at a high level, so some cross-namespace introspection functions like this would be useful. |
||||||||||||||
|
||||||||||||||
$$\verb|storage_usage()| \ge \sum_{n} \verb|namespace_storage_usage| (n)$$ | ||||||||||||||
|
||||||||||||||
There is no need for a `signer_namespace` function, because a signer is never a smart contract, and can therefore never have a non-empty namespace. | ||||||||||||||
|
||||||||||||||
Some existing host functions will need to be updated to include namespaces: | ||||||||||||||
encody marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
|
||||||||||||||
- `promise_batch_action_function_call`: Calls default namespace. Introduced `promise_batch_action_function_call_ns` to call a specific namespace. | ||||||||||||||
- `promise_batch_action_deploy_contract`: Targets default namespace. Introduced `promise_batch_action_deploy_contract_ns` to target a specific namespace. | ||||||||||||||
- etc. | ||||||||||||||
|
||||||||||||||
Existing host functions will generally still refer to the account as a whole: | ||||||||||||||
|
||||||||||||||
- `storage_usage() -> u64`: Returns the storage usage of the current account, including all namespaces. | ||||||||||||||
encody marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the current proposal, then there are two ways of getting storage used. In the ideal world, it would be great to have a single hostfunction with a signature like: If it does, I propose that we update the proposed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think I have enough information to have an informed opinion here. Making a decision one way or another has the potential to break contracts: if |
||||||||||||||
- `account_balance() -> u128`: Returns the entire balance of the current account. There is no namespace-specific accounting. | ||||||||||||||
|
||||||||||||||
### RPC view calls | ||||||||||||||
|
||||||||||||||
#### `view_account` | ||||||||||||||
|
||||||||||||||
The `code_hash` field contains the hash of the code deployed to the default namespace. The `code_hashes` field contains a map of namespaces to code hashes, including the default namespace. (This means that the hash of the code deployed to the default namespace is returned twice. This is intentional, for backwards compatibility.) | ||||||||||||||
|
||||||||||||||
#### `view_state` | ||||||||||||||
|
||||||||||||||
The `view_state` call now accepts an optional `namespace` field. If it is not specified, the default namespace is used. The `view_state` call is then executed on the state associated with the specified namespace. | ||||||||||||||
|
||||||||||||||
#### `view_code` | ||||||||||||||
|
||||||||||||||
The `view_code` call now accepts an optional `namespace` field. If it is not specified, the default namespace is used. The `view_code` call then returns the code deployed on the specified namespace. | ||||||||||||||
|
||||||||||||||
## Reference Implementation | ||||||||||||||
|
||||||||||||||
- https://github.com/near/nearcore/pull/8890 | ||||||||||||||
|
||||||||||||||
## Security Implications | ||||||||||||||
|
||||||||||||||
- Applications that detect smart contract updates by tracking the `code_hash` field from a `view_account` call will fail to account for updates to namespaces. (Note that the correct way to track code changes is by monitoring the blockchain for `DeployContract` actions targeting the account, not by tracking `code_hash`.) | ||||||||||||||
- As described by this NEP, namespaces all have full permission to act on behalf of the account, just as any smart contract. | ||||||||||||||
- If a namespaced contract interacts with a legacy contract (unaware of namespaces), it is possible that the legacy contract may save the account ID of the namespaced contract, but not the namespace. If the legacy contract subsequently attempts to interact with the namespaced contract, it will only be able to interact with the contract deployed to the default namespace instead. However, this is equivalent to the case in which an non-contract account signs the same set of actions to the legacy contract. | ||||||||||||||
|
||||||||||||||
## Drawbacks (Optional) | ||||||||||||||
|
||||||||||||||
## Unresolved Issues (Optional) | ||||||||||||||
Comment on lines
+106
to
+108
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
|
||||||||||||||
- How to delete a namespace? | ||||||||||||||
- How to enumerate namespaces from within a smart contract? | ||||||||||||||
- Backwards compatibility issues could be resolved with a per-account routing table that maps incoming method names to a [namespace, method name] pair. | ||||||||||||||
|
||||||||||||||
## Alternatives | ||||||||||||||
|
||||||||||||||
- Only using a routing table. However, this increases complexity for end-users significantly. | ||||||||||||||
|
||||||||||||||
## Future possibilities | ||||||||||||||
|
||||||||||||||
- Sync execution between namespaces. | ||||||||||||||
- Permissioned namespaces. | ||||||||||||||
- Codeless contracts / contract subscriptions / global contracts. | ||||||||||||||
|
||||||||||||||
See [the Account Extensions upgrade master issue](https://github.com/near/NEPs/issues/478). | ||||||||||||||
|
||||||||||||||
## Changelog | ||||||||||||||
|
||||||||||||||
### 1.0.0 - Initial Version | ||||||||||||||
|
||||||||||||||
#### Benefits | ||||||||||||||
|
||||||||||||||
- Easier contract composability, e.g. just deploy the NEP-141 and NEP-330 modules and you’re set. Good for developers (esp. w.r.t. contract factories) and non-developer users (one-click deploy composable modules). | ||||||||||||||
- Safer and easier contract upgrades, e.g. have an upgrade_controller namespace that manages upgrading the rest of the contract’s components, so a bad upgrade doesn’t brick the account. | ||||||||||||||
|
||||||||||||||
#### Concerns | ||||||||||||||
|
||||||||||||||
## Copyright | ||||||||||||||
|
||||||||||||||
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). | ||||||||||||||
|
||||||||||||||
## References | ||||||||||||||
|
||||||||||||||
- https://gov.near.org/t/proposal-account-extensions-contract-namespaces/34227 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does not explain how namespaces enable this feature. More specifically, what is the limitation of the current protocol that is eliminated with this proposal
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Workflow example:
alice.near
.v1
to the default namespace.upgrade-manager
to theupgrade-manager
namespace. Theupgrade-manager
bytecode provides anupgrade
method that performs a contract upgrade sequence (explained in later steps).alice.near
.v2
.alice.near:upgrade-manager->upgrade(v2)
, passing in thev2
bytecode.upgrade-manager
code performs a contract upgrade sequence, deploying thev2
bytecode to the default namespace ofalice.near
.v2
bytecode is invalid/unusable.v3
.upgrade-manager
to perform the upgrade sequence withv3
.If namespaces had not existed, the
v2
deployment would have bricked the entirealice.near
account.