Skip to content
Merged
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
10 changes: 9 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,15 @@ jobs:
platform: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4

- name: Install Linux dependencies
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y libudev-dev pkg-config

- name: Install cargo-near
run: cargo install --locked cargo-near

- name: Install and test modules
run: |
cargo test
20 changes: 7 additions & 13 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,32 +1,26 @@
[package]
name = "contract"
description = "Factory Contract Example"
name = "factory-contract-global"
version = "0.1.0"
edition = "2021"
# TODO: Fill out the repository field to help NEAR ecosystem tools to discover your project.
# NEP-0330 is automatically implemented for all contracts built with https://github.com/near/cargo-near.
# Link to the repository will be available via `contract_source_metadata` view-function.
#repository = "https://github.com/xxx/xxx"

[lib]
crate-type = ["cdylib", "rlib"]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
near-sdk = { version = "5.3.0", features = ["unstable"] }
borsh = "1.5.7"
bs58 = "0.5.1"
near-sdk = { version = "5.17.1", features = ["global-contracts", "unstable"] }

[dev-dependencies]
near-sdk = { version = "5.3.0", features = ["unit-testing"] }
near-workspaces = { version = "0.16.0", features = ["unstable"] }
near-sdk = { version = "5.17.1", features = ["global-contracts", "unit-testing"] }
near-workspaces = { version = "0.21", features = ["unstable"] }
tokio = { version = "1.12.0", features = ["full"] }
serde_json = "1"
anyhow = "1.0"

[profile.release]
codegen-units = 1
# Tell `rustc` to optimize for small code size.
opt-level = "z"
lto = true
debug = false
panic = "abort"
# Opt into extra safety checks on arithmetic operations https://stackoverflow.com/a/64136471/249801
overflow-checks = true
209 changes: 44 additions & 165 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,198 +1,77 @@
# Factory Contract Example

A factory is a smart contract that stores a compiled contract on itself, and
automatizes deploying it into sub-accounts.

This particular example presents a factory of donation contracts, and enables
to:

1. Create a sub-account of the factory and deploy the stored contract on it
(create_factory_subaccount_and_deploy).
2. Change the stored contract using the update_stored_contract method.

```rust
#[payable]
pub fn create_factory_subaccount_and_deploy(
&mut self,
name: String,
beneficiary: AccountId,
public_key: Option<PublicKey>,
) -> Promise {
// Assert the sub-account is valid
let current_account = env::current_account_id().to_string();
let subaccount: AccountId = format!("{name}.{current_account}").parse().unwrap();
assert!(
env::is_valid_account_id(subaccount.as_bytes()),
"Invalid subaccount"
);

// Assert enough tokens are attached to create the account and deploy the contract
let attached = env::attached_deposit();

let code = self.code.clone().unwrap();
let contract_bytes = code.len() as u128;
let minimum_needed = NEAR_PER_STORAGE.saturating_mul(contract_bytes);
assert!(
attached >= minimum_needed,
"Attach at least {minimum_needed} yⓃ"
);

let init_args = near_sdk::serde_json::to_vec(&DonationInitArgs { beneficiary }).unwrap();

let mut promise = Promise::new(subaccount.clone())
.create_account()
.transfer(attached)
.deploy_contract(code)
.function_call(
"init".to_owned(),
init_args,
NO_DEPOSIT,
TGAS.saturating_mul(5),
);

// Add full access key is the user passes one
if let Some(pk) = public_key {
promise = promise.add_full_access_key(pk);
}

// Add callback
promise.then(
Self::ext(env::current_account_id()).create_factory_subaccount_and_deploy_callback(
subaccount,
env::predecessor_account_id(),
attached,
),
)
}
```
# Factory Contract with Global Contracts Example

## How to Build Locally?
This example demonstrates how to use NEAR's global contract functionality to deploy and use global smart contracts.

Install [`cargo-near`](https://github.com/near/cargo-near) and run:
Global contracts allow sharing contract code globally across the NEAR network, reducing deployment costs and enabling efficient code reuse.

```bash
cargo near build
```
## Key Features

## How to Test Locally?
- Deploy a global contract using `deploy_global_contract()`
- Use an existing global contract by hash with `use_global_contract()`
- Use an existing global contract by deployer account with `use_global_contract_by_account_id()`
- Integration tests using near-workspaces

```bash
cargo test
```
## Install `cargo-near` build tool

## How to Deploy?
See [`cargo-near` installation](https://github.com/near/cargo-near#installation)

Deployment is automated with GitHub Actions CI/CD pipeline. To deploy manually,
install [`cargo-near`](https://github.com/near/cargo-near) and run:
## Build with:

```bash
cargo near deploy <account-id>
cargo near build
```

## How to Interact?

_In this example we will be using [NEAR CLI](https://github.com/near/near-cli)
to intract with the NEAR blockchain and the smart contract_

_If you want full control over of your interactions we recommend using the
[near-cli-rs](https://near.cli.rs)._

### Deploy the Stored Contract Into a Sub-Account

`create_factory_subaccount_and_deploy` will create a sub-account of the factory
and deploy the stored contract on it.
## Run Tests:

### Unit Tests
```bash
near call <factory-account> create_factory_subaccount_and_deploy '{ "name": "sub", "beneficiary": "<account-to-be-beneficiary>"}' --deposit 1.24 --accountId <account-id> --gas 300000000000000
cargo test
```

This will create the `sub.<factory-account>`, which will have a `donation`
contract deployed on it:

### Integration Tests
```bash
near view sub.<factory-account> get_beneficiary
# expected response is: <account-to-be-beneficiary>
cargo test --test workspaces
cargo test --test realistic
```

### Update the Stored Contract

`update_stored_contract` enables to change the compiled contract that the
factory stores.

The method is interesting because it has no declared parameters, and yet it
takes an input: the new contract to store as a stream of bytes.

To use it, we need to transform the contract we want to store into its `base64`
representation, and pass the result as input to the method:
## Create testnet dev-account:

```bash
# Use near-cli to update stored contract
export BYTES=`cat ./src/to/new-contract/contract.wasm | base64`
near call <factory-account> update_stored_contract "$BYTES" --base64 --accountId <factory-account> --gas 30000000000000
cargo near create-dev-account
```

> This works because the arguments of a call can be either a `JSON` object or a
> `String Buffer`

## Factories - Explanations & Limitations
## Deploy to dev-account:

Factories are an interesting concept, here we further explain some of their
implementation aspects, as well as their limitations.
```bash
cargo near deploy
```

<br>
## How Global Contracts Work

### Automatically Creating Accounts
1. **Deploy Global Contract**: A contract deploys bytecode as a global contract, making it available network-wide
2. **Use by Hash**: Other contracts can reference the global contract by its code hash
3. **Use by Account**: Contracts can reference a global contract by the account that deployed it

NEAR accounts can only create sub-accounts of themselves, therefore, the
`factory` can only create and deploy contracts on its own sub-accounts.
This reduces storage costs and enables code sharing across the ecosystem.

This means that the factory:
## Use Cases from NEP-591

1. **Can** create `sub.factory.testnet` and deploy a contract on it.
2. **Cannot** create sub-accounts of the `predecessor`.
3. **Can** create new accounts (e.g. `account.testnet`), but **cannot** deploy
contracts on them.
- **Multisig Contracts**: Deploy once, use for many wallets without paying 3N each time
- **Smart Contract Wallets**: Efficient user onboarding with chain signatures
- **Business Onboarding**: Companies can deploy user accounts cost-effectively
- **DeFi Templates**: Share common contract patterns across protocols

It is important to remember that, while `factory.testnet` can create
`sub.factory.testnet`, it has no control over it after its creation.
## Runtime Requirements

### The Update Method
⚠️ **Important**: Global contracts are not yet available in released versions of nearcore.

The `update_stored_contracts` has a very short implementation:
- **Current Status**: Global contract host functions are implemented in nearcore but will first be available in version 2.7.0
- **SDK Status**: This near-sdk-rs implementation is ready and waiting for runtime support
- **Testing**: Integration tests require a custom nearcore build with global contract support

```rust
#[private]
pub fn update_stored_contract(&mut self) {
self.code.set(env::input());
}
```
### When Available

On first sight it looks like the method takes no input parameters, but we can
see that its only line of code reads from `env::input()`. What is happening here
is that `update_stored_contract` **bypasses** the step of **deserializing the
input**.

You could implement `update_stored_contract(&mut self, new_code: Vec<u8>)`,
which takes the compiled code to store as a `Vec<u8>`, but that would trigger
the contract to:

1. Deserialize the `new_code` variable from the input.
2. Sanitize it, making sure it is correctly built.

When dealing with big streams of input data (as is the compiled `wasm` file to
be stored), this process of deserializing/checking the input ends up **consuming
the whole GAS** for the transaction.

## Useful Links

- [cargo-near](https://github.com/near/cargo-near) - NEAR smart contract
development toolkit for Rust
- [near CLI-rs](https://near.cli.rs) - Iteract with NEAR blockchain from command
line
- [NEAR Rust SDK Documentation](https://docs.near.org/sdk/rust/introduction)
- [NEAR Documentation](https://docs.near.org)
- [NEAR StackOverflow](https://stackoverflow.com/questions/tagged/nearprotocol)
- [NEAR Discord](https://near.chat)
- [NEAR Telegram Developers Community Group](https://t.me/neardev)
- NEAR DevHub: [Telegram](https://t.me/neardevhub),
[Twitter](https://twitter.com/neardevhub)
Once nearcore 2.7.0 is released, you'll be able to:
- Deploy global contracts on mainnet and testnet
- Run integration tests with near-workspaces using version "2.7.0" or later
- Use all the functionality demonstrated in this example
6 changes: 3 additions & 3 deletions rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[toolchain]
channel = "1.86.0"
components = ["rustfmt", "clippy", "rust-analyzer"]
targets = ["wasm32-unknown-unknown"]
channel = "1.86"
components = ["rustfmt"]
targets = ["wasm32-unknown-unknown"]
87 changes: 0 additions & 87 deletions src/deploy.rs

This file was deleted.

Binary file removed src/donation-contract/donation.wasm
Binary file not shown.
Loading