-
Notifications
You must be signed in to change notification settings - Fork 148
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]: Global Contracts #591
base: master
Are you sure you want to change the base?
Changes from all commits
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,207 @@ | ||
--- | ||
NEP: 591 | ||
Title: Global Contracts | ||
Authors: Bowen Wang <bowen@nearone.org>, Anton Puhach <anton@nearone.org>, Stefan Neamtu <stefan.neamtu@nearone.org> | ||
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. | ||
Users can use contracts deployed by hash if they prefer having control over contract updates. In order to update the contract user would have to explicitly switch to a different version of the contract deployed under a different hash. | ||
Contracts deployed by account id should be used when user trusts contract developers to update the contract for them. For example if user accounts are created specifically for some application to be onchain. | ||
|
||
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 deployment 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` | ||
|
||
Using a global contract incurs two costs, as follows: | ||
|
||
* `action_use_global_contract` | ||
* mirrors `action_deploy_contract` | ||
* base cost for processing a usage action | ||
* `action_use_global_contract_per_identifier_byte` | ||
* mirrors `action_deploy_contract_per_byte`, but based on the global contract identifier length | ||
* introduced because the `AccountId` in `GlobalByAccount(AccountId)` variant can vary in length, unlike `Global(CryptoHash)` with a fixed size of 32 bytes | ||
|
||
Referencing a global contract locks tokens for storage in accordance with `storage_amount_per_byte` based on global contract identifier length, calculated similarly to `action_use_global_contract_per_identifier_byte`. | ||
|
||
Although the cost structure is similar to that of single shard contract deployments, the overall fees are significantly lower because only a few bytes are stored for the reference. This is desired, because referencing a global contract is not an expensive operation for the network. | ||
|
||
## 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) 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. Yes, I don't have anything to say in those section, so it was omitted 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've added Future possibilities section in 053cb24 mentioning sharded contracts |
||
## Future possibilities | ||
|
||
Global contracts can potentially be used as part of the sharded contracts effort. Sharded contract code should be available on all shards, so using global contracts for that might be a good choice. | ||
|
||
## 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/). |
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?
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.
Sure, added in bfad2e3