Skip to content
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
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ Changes to the protocol specification and standards are called NEAR Enhancement
| [0519](https://github.com/near/NEPs/blob/master/neps/nep-0519.md) | Yield Execution | @akhi3030 @saketh-are | Final |
| [0536](https://github.com/near/NEPs/blob/master/neps/nep-0536.md) | Reduce the number of gas refunds | @evgenykuzyakov @bowenwang1996 | Final |
| [0539](https://github.com/near/NEPs/blob/master/neps/nep-0539.md) | Cross-Shard Congestion Control | @wacban @jakmeier | Final |
| [0539](https://github.com/near/NEPs/blob/master/neps/nep-0591.md) | Global Contracts | @bowenwang1996 @pugachag @stedfn | Final |

## Specification

Expand Down
190 changes: 190 additions & 0 deletions neps/nep-0591.md
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.
Comment on lines +35 to +37
Copy link
Contributor

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?


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.

Copy link
Contributor

Choose a reason for hiding this comment

The 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/).