-
Notifications
You must be signed in to change notification settings - Fork 146
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-591]: [WIP] Global Contracts #591
Draft
pugachAG
wants to merge
1
commit into
master
Choose a base branch
from
global-contracts
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+191
−0
Draft
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
--- | ||
NEP: 591 | ||
Title: Global Contracts | ||
Authors: Bowen Wang <[email protected]>, Anton Puhach <[email protected]>, Stefan Neamtu <[email protected]> | ||
Status: Draft | ||
DiscussionsTo: https://github.com/nearprotocol/neps/pull/591 | ||
Type: Protocol | ||
Replaces: 491 | ||
Version: 1.0.0 | ||
Created: 2025-02-11 | ||
LastUpdated: 2025-02-11 | ||
--- | ||
|
||
## Summary | ||
|
||
This proposal introduces global contracts, a new mechanism that allows smart contracts to be deployed once and reused by any account without incurring high storage costs. | ||
|
||
Currently, deploying the same contract multiple times on different accounts leads to significant storage fees. | ||
Global contracts solve this by making contract code available globally, allowing multiple accounts to reference it instead of storing their own copies. | ||
|
||
Rather than requiring full storage costs for each deployment, accounts can simply link to an existing global contract, reducing redundancy and improving scalability. This approach optimizes storage, lowers costs, and ensures efficient contract distribution across the network. | ||
|
||
## Motivation | ||
|
||
A common use case on NEAR is to deploy the same smart contract many times on many different accounts. For example, a multisig contract is a frequently deployed contract. | ||
However, today each time such a contract is deployed, a user has to pay for its storage and the cost is quite high. For a 300kb contract the cost is 3N. | ||
|
||
With the advent of chain signatures, the smart contract wallet use case will become more ubiquitous. | ||
As a result, it is very desirable to be able to reuse already deployed contract without having to pay for the storage cost again. | ||
|
||
Additionally global contracts cover the underlying use case for [NEP-491](https://github.com/near/NEPs/pull/491): https://github.com/near/nearcore/pull/12818. | ||
|
||
## Specification | ||
|
||
Global contract can be deployed in 2 ways: either by its hash or by owner account id. | ||
Contracts deployed by hash are effectively immutable and cannot be updated. | ||
When deployed by account id the owner can redeploy the contract updating it for all its users. | ||
|
||
We introduce new receipt action for deploying global contracts: | ||
|
||
```rust | ||
struct DeployGlobalContractAction { | ||
code: Vec<u8>, | ||
deploy_mode: GlobalContractDeployMode, | ||
} | ||
|
||
enum GlobalContractDeployMode { | ||
/// Contract is deployed under its code hash. | ||
/// Users will be able reference it by that hash. | ||
/// This effectively makes the contract immutable. | ||
CodeHash, | ||
/// Contract is deployed under the owner account id. | ||
/// Users will be able reference it by that account id. | ||
/// This allows the owner to update the contract for all its users. | ||
AccountId, | ||
} | ||
``` | ||
|
||
Also new action is added for using previously deployed global contract: | ||
|
||
```rust | ||
struct UseGlobalContractAction { | ||
contract_identifier: GlobalContractIdentifier, | ||
} | ||
|
||
enum GlobalContractIdentifier { | ||
CodeHash(CryptoHash), | ||
AccountId(AccountId), | ||
} | ||
``` | ||
|
||
## Reference Implementation | ||
|
||
### Storage | ||
|
||
In order to have global contracts available to users on all shards we store a copy in each shard's trie. | ||
A new trie key is introduced for that: | ||
|
||
```rust | ||
pub enum TrieKey { | ||
... | ||
GlobalContractCode { | ||
identifier: GlobalContractCodeIdentifier, | ||
}, | ||
} | ||
|
||
pub enum GlobalContractCodeIdentifier { | ||
CodeHash(CryptoHash), | ||
AccountId(AccountId), | ||
} | ||
``` | ||
|
||
The value is contract code bytes, similar to `TrieKey::ContractCode`. | ||
|
||
### Distribution | ||
|
||
Global contract has to be distributed to all shards after being deployed. | ||
This is implemented with a dedicated receipt type: | ||
|
||
```rust | ||
enum ReceiptEnum { | ||
... | ||
GlobalContractDistribution(GlobalContractData), | ||
} | ||
|
||
struct GlobalContractData { | ||
code: Vec<u8>, | ||
id: GlobalContractIdentifier, | ||
} | ||
``` | ||
|
||
`GlobalContractDistribution` receipt is generated as a result of processing `DeployGlobalContractAction`. | ||
Receipt distribution logic is updated to route such receipts to all shards. | ||
So effectively it is a part of `ShardProof` (incoming receipts) for each shard, but occurs only once in the outgoing receipts of that chunk. | ||
Applying `GlobalContractDistribution` receipt updates the corresponding `TrieKey::GlobalContractCode` in the trie. | ||
|
||
### Usage | ||
|
||
We change `Account` struct to make it possible to reference global contracts. | ||
`AccountV2` is introduced changing `code_hash: CryptoHash` field to more generic `contract: AccountContract`: | ||
|
||
```rust | ||
enum AccountContract { | ||
None, | ||
Local(CryptoHash), | ||
Global(CryptoHash), | ||
GlobalByAccount(AccountId), | ||
} | ||
``` | ||
|
||
Applying `UseGlobalContractAction` updates user account `contract` field accordingly. | ||
|
||
`FunctionCall` action processing is updated to respect global contracts. This includes updating [contract preparation pipeline](https://github.com/near/nearcore/blob/fb95d7b7740d1fda9245afa498ce4e9ac145c8af/runtime/runtime/src/pipelining.rs#L24) as well as [recording of the executed contract to be included in the state witness](https://github.com/near/nearcore/blob/fb95d7b7740d1fda9245afa498ce4e9ac145c8af/core/store/src/trie/update.rs#L338). | ||
|
||
### Costs | ||
|
||
For global contracts we burn tokens for storage instead of locking like what we do regular contracts today. | ||
The cost per byte of global contract code `global_contract_storage_amount_per_byte` is set as 10x the storage staking cost `storage_amount_per_byte`. | ||
|
||
Additionally we add action costs for the global contract related actions: | ||
|
||
* `action_deploy_global_contract` is exactly the same as `action_deploy_contract` | ||
* `action_deploy_global_contract_per_byte`: | ||
* send costs are the same as `action_deploy_contract_per_byte` | ||
* execution costs should cover distribution of the contract to all shards: | ||
* this is pretty expensive for the network, so want want to change significant amount of gas for that | ||
* we still want to be able to fit max allowed contracts size into single chunk space: `max_gas_burnt = 300_000_000_000_000`, `max_contract_size = 4_194_304`, so it should be at most `max_gas_burnt / max_contract_size = 71_525_573` | ||
* we need to allow for some margin for other costs, so we can round it down to `70_000_000` | ||
|
||
TODO(stedfn): cover costs for using global contracts | ||
|
||
## Security Implications | ||
|
||
One potential issue is increasing infrastructure cost for global contracts with growing number of shards. | ||
A global contract is effectively replicated on every shard, so with increase in number of shards each global contract uses more storage. | ||
This can be potentially addressed in the future by making deployment costs parametrized with the number of shards in the current epoch, but it still wouldn't address the issue for the already deployed contracts. | ||
|
||
## Alternatives | ||
|
||
In [the original proposal](https://github.com/near/NEPs/issues/556) we considered storing global contracts in a separate global trie (managed at the block level) and introducing a dedicated distribution mechanism. | ||
We decided not to proceed with this approach as it requires significantly higher effort to implement and also introduces new critical dependencies for the protocol. | ||
|
||
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. Did you intentionally skip Future possibilities and Consequences section? (e.g. https://github.com/near/NEPs/blob/global-contracts/neps/nep-0539.md#future-possibilities) |
||
## Changelog | ||
|
||
[The changelog section provides historical context for how the NEP developed over time. Initial NEP submission should start with version 1.0.0, and all subsequent NEP extensions must follow [Semantic Versioning](https://semver.org/). Every version should have the benefits and concerns raised during the review. The author does not need to fill out this section for the initial draft. Instead, the assigned reviewers (Subject Matter Experts) should create the first version during the first technical review. After the final public call, the author should then finalize the last version of the decision context.] | ||
|
||
### 1.0.0 - Initial Version | ||
|
||
> Placeholder for the context about when and who approved this NEP version. | ||
#### Benefits | ||
|
||
> List of benefits filled by the Subject Matter Experts while reviewing this version: | ||
* Benefit 1 | ||
* Benefit 2 | ||
|
||
#### Concerns | ||
|
||
> Template for Subject Matter Experts review for this version: | ||
> Status: New | Ongoing | Resolved | ||
| # | Concern | Resolution | Status | | ||
| --: | :------ | :--------- | -----: | | ||
| 1 | | | | | ||
| 2 | | | | | ||
|
||
## Copyright | ||
|
||
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Can we talk about when to use each way? For instance, when should a user use hash instead of account id, and vice versa?